ST310 Course Project

The aim of this project is to demonstrate the application of machine learning techniques discussed in the ST310 Machine Learning module, in turn drawing primarily from the exposition in (James, Gareth and Witten, Daniela and Hastie, Trevor and Robert Tibshirani 2017) and (Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome 2009) among other texts. We examine the use of both linear techniques, e.g. Logistic Regression, and non-linear methods, e.g. Random Forests and Gradient Boosted Decision Trees, for a generic binary classification problem.

Remark: This report is best viewed as a .html file. If executing this notebooks as a .Rmd file, ensure that all libraries and dependencies are installed and that the chunks are executed in sequential order.

Dataset

We obtain the dataset from the Kaggle March Tabular Playground competition. The data consists of anonymised features, which correspond to a binary outcome variable; in other words the task is a classification problem. Although the data is anonymised, the challenge description states:

The dataset used for this competition is synthetic but based on a real dataset and generated using a CTGAN. The original dataset deals with predicting the amount of an insurance claim. Although the features are anonymized, they have properties relating to real-world features.

We believe that this machine learning task is well-motivated, as predicting the amount of an insurance claim or whether an insurance claim occurred, would have a meaningful business impact in the context of actuarial pricing and risk management.

# generic functions and plotting
library(tidyverse)
library(tidyr)
library(ggplot2) 
library(GGally) 
library(patchwork)
library(broom)
library(data.table)
library(doParallel)
library(foreach)
# modelling
library(caret)
library(tidymodels)
library(glmnet)
library(randomForest)
library(xgboost)
library(catboost)
# metrics
library(car) #outliers
library(pROC)
library(pdp)
# read in data
df <- read.csv(file = "../data/train.csv")
cat(c("The dimensions of the array are :", dim(df)[1], ", ", dim(df)[2]))
The dimensions of the array are : 300000 ,  32

The dataset consists of 30 features, 19 categorical variables and 11 numerical variables, and a binary outcome variable target. We will need to process these categorical variables in some manner, which we shall consider in the subsequent section.

Preprocessing

We remove the first column id, which represents an unique identifier for each observation. We then convert the first 19 columns, which are categorical variables into the factor data type in R. Given computational limitations (the code is executed on single CPU laptops), we subsample n_train = 4000 observations from the complete dataset of 300,000 observations. For our subsequent analysis, we will use only this subsample of 4000 observations, although the approaches can be naturally extended to a larger dataset given greater computational resources. We further partition the 4000 observations, into a train set of 3000 observations and a test set of 1000 observations. All training and tuning are performed on the training data, and we will consider model performance on the test set performance scores.

Remark: Different models would require different preprocessing for the categorical variables, and thus we will further address in subsequent sections; for example the glm package can accept factors as a data type, whereas for glmnet they must be converted to the model.matrix format.

Remark: When encoding the categorical variables as dummy variables in our subsequent analysis, we find that there are over 600 unique categories across all 19 of the categorical variables. We could also consider different methods to process categorical variables, such as merging less frequently occurring categories, or target encoding (Parr, Terence and Howard, Jeremy 2019, ch. 6). Nonetheless, we stick with the factors and dummies approach given our lack of familiarity with these other methods.

Feature Engineering

A challenge in this case is that we have no specific domain knowledge about the meaning of particular features, and whether they may be useful. Thus in this problem, any preprocessing in terms of feature engineering, and a priori feature selection may not be that meaningful. Nonetheless, we can resort to model interpretability methods (Molnar, Christoph 2019) to obtain an ex-post explanation of our models. In the linear case, we can examine coefficients and their statistical significance, and in the case of tree-based models, such as Feature Importances, Partial Dependence Plots, and Shapley values.

cat_feats = 1:19 # the first 19 columns are categorical 
cont_feats <- 20:30 # and the next 11 are continuous
target_col <- 31 # target
# convert cats to factors
df <- column_to_rownames(df, var = "id") %>% 
         mutate_if(is.character,as.factor) %>% 
         mutate_at(vars(target), factor)
# subsample the data for faster model inference
set.seed(1)
n_train <- 4000
df_sample = df[sample(1:nrow(df), n_train),]
# Partition data into train and test; test will be our oos data
set.seed(1)
df_split <- initial_split(df_sample, prop = 3/4)
df_train <- training(df_split)
df_test <- testing(df_split)

Exploratory Data Analysis

We first conduct some exploratory data analysis by visualising the univariate and bivariate relationships in the dataset. This allows us to gain some understanding of the data and potential modelling approaches.

Univariate EDA

We observe that the univariate distributions of the continuous variables are all multi-modal and non-normal, but they are all normalised (scaled) to the range of \([0, 1]\).

# flatten df into using pivot_longer and plot distribution
df %>% pivot_longer(cols = starts_with("cont"), names_to  = "cont") %>% 
   ggplot(aes(x = value))+
   geom_histogram(bins = 100, alpha = 0.85)+
   ggtitle("Continuous features distribution")+
   facet_wrap(cont~., scales = "free") +
   theme_minimal()

From the distributions of categorical variables, we see that there are variables with substantially more observations in one category, and also variables with a high number of (\(>50\)) categories, which will be an issue to address for any subsequent preprocessing.

# flatten df into using pivot_longer and plot distribution
df %>% pivot_longer(cols = contains(c("cat", "target")), names_to  = "cat") %>% 
   ggplot(aes(x = value))+
   geom_bar(alpha = 0.85)+
   ggtitle("Categorical features distribution")+
   facet_wrap(cat~., scales = "free", ncol = 4) +
   theme_minimal(base_size = 30)

Bivariate EDA

We group the continuous variables by the target and plot them as boxplots to check for any obvious differences discernible by eye. From the plots, claims with target == 1 have lower values of cont3 on average (median) than claims with target == 0. Claims with target=1 also have a much higher median value of cont4 than claims with target == 0. Hence, we would expect cont3 and cont4 to have negative relationships with target. In addition, we see a group of observations at the tail ends for cont0, cont5, cont7, cont8, cont9, cont10, which may be indicative of outliers.

# flatten df into using pivot_longer
# group by target and plot distribution
df[, c(cont_feats, target_col)] %>% 
  pivot_longer(cols = starts_with("cont"), names_to  = "var", values_to="value") %>% 
  ggplot(aes(x=target,y=value), fill=factor(value)) + 
  geom_boxplot() + coord_flip() + facet_wrap(~var, scales="free_x")

From our conditional boxplots, we can identify potential outliers by filtering observations that lie at the extreme percentiles of those continuous variables. In particular we note that there are many values at the extreme quantiles for the variables cont0, cont5, cont7, cont8, cont9, cont10. We investigate this further using the Hampel filter, which considers points lying outside the median plus or minus 3 mean absolute deviations as outliers. Through this approach, more than 200 observations per variable would be identified as outliers, which suggests that these may not be outliers but that the distribution is just heavy-tailed. Without further information on the reasonable scale of values that individual variables can take (along with the fact that they are all normalised), we find it challenging to identify outliers through descriptive statistics and decide not to exclude any such points using this approach. We will proceed to detect outliers in another approach in subsequent modelling.

# MAD filter
hampel_filter <- function(df){
   lower_bound <- median(df) - 3 * mad(df, constant = 1)
   upper_bound <- median(df) + 3 * mad(df, constant = 1)
   outlier_ind <- which(df < lower_bound | df > upper_bound)
   return(outlier_ind)
}
# quantile filter
percentile_filter <- function(df, lq = 0.001, uq = 0.999){
   lower_bound <- quantile(df, lq)
   upper_bound <- quantile(df, uq)
   outlier_ind <- which(df < lower_bound | df > upper_bound)
   return(outlier_ind)
}
# outlier count
hampel_count <- function(x){length(hampel_filter(x))}
pct_count <- function(x){length(percentile_filter(x))}
outlier_counts <- df_train[, cont_feats] %>% map_dfr(hampel_count)
outlier_counts[2, ] <- df_train[, cont_feats] %>% map_dfr(pct_count)
outlier_counts

For categorical variables, we use stacked bar plots to show the percentages of observations in each category that correspond to target == 0 and target == 1 respectively. A much larger proportion of claims with cat13 == B appear to be associated with target == 1 compared to cat13 == A. On the other hand, a much larger percentage of claims correspond to target == 0 if cat18 is A or B, than if cat18 is C or D.

# flatten df into using pivot_longer
# group by target and plot distribution
df[, c(cat_feats, target_col)] %>% 
  pivot_longer(cols = starts_with("cat"), names_to  = "cat", values_to="value") %>% 
  ggplot(aes(x = value, fill=target)) + 
    geom_bar(position="fill") + 
    scale_y_continuous(name = "Within group Percentage", labels = scales::percent) +
    facet_wrap(~cat, scales="free_x", ncol = 4) +
    theme_minimal(base_size = 40)

We also inspect the correlation matrix for our continuous variables. There seems to be a cluster of variables cont1, cont2, cont8 that are highly correlated with each other. This may be potentially indicative of multicollinearity, a concern when using linear models. In addition, we also consider the (Pearson) correlation and our continuous variables, although it is not necessarily meaningful in this case given our target is binary.

We note that the continuous variables have Pearson correlations of roughly \([-0.2, 0.2]\), suggesting that there is some small signal or information between the features and the target. However, the Pearson correlation only describes a linear and pairwise association between the variables. As such there could also be complex non-linear associations and interactions between the variables, which the presence of multi-modal distributions of the continuous features in our univariate EDA may also affirm.

#heatmap, high values in red, low in yellow
cor_matrix <- cor(df[, cont_feats])
heatmap(cor_matrix, main="Correlation Matrix (Clustered)")

cor_with_target <- rownames_to_column(data.frame(cor(df[, c(cont_feats)], as.numeric(df$target))))
names(cor_with_target) <- c("feature", "pearson_corr")
cor_with_target %>%
  ggplot(aes(x=reorder(feature, -pearson_corr), y = pearson_corr)) + 
  geom_bar(stat='identity') + coord_flip() + ggtitle("Pearson Correlation of Continuous with Target")

We can use Principal Components Analysis (PCA) as a means to visualise the data in low-dimension, and examine whether there are any explicitly discernible trends. We apply PCA to all the continuous features, without further pre-scaling is required given the continuous features have values from \([0, 1]\). By eye, there do not appear to be any significant difference in the PCA representations for each class, although there are many observations with target == 1 closer to the bottom of the ellipsoid formed by the first two PCA loadings. At least in the space formed by the first two princial components, the classes do not appear to be linearly separable, - which suggest a non-linear method may be more effective on this classification task.

On examination of the cumulative explained variance ratio, the variation in \(\mathbf{X}\) captured by \(k\) principal components, we find that the first two principal components only capture about \(\approx 60\%\) variation in the continuous variables. To capture \(\approx 90\%\) of the variation, we would need 6 of the continuous features, and for $% we would need 9. This could suggest that including more features could be beneficial, which would be a concern if we are to do feature selection.

# pca loadings
pcs <- prcomp(df[,cont_feats])
set.seed(2021)
data.frame(pc1=pcs$x[,1], pc2=pcs$x[,2], target=df[, "target"]) %>%
ggplot(aes(x = pc1, y = pc2, colour = target)) + 
  geom_jitter(alpha=0.7) + ggtitle('Principal Components')

# cumulative explained variance ratio
cumul_var <- cumsum(pcs$sdev^2 / sum(pcs$sdev^2))
ggplot(data.frame(feature = 1:11, cumul_var = cumul_var)) + 
  geom_line(aes(x = feature,y = cumul_var)) + ggtitle("Cumulative Explained Variance Ratio") +
  scale_y_continuous(labels=percent) + xlab("Number of Principal Components") + 
  ylab("Cumulative explained Variance Ratio")

Modelling

We first consider a naive model that always predicts a single label (in this case the greater accuracy is obtained by always predicting \(0\), given it is the most frequently occurring class). The accuracy in this case, the proportion of correct labels \(\sum_{i = 1}^{n} I(y_{i} = \hat{y_{i}})\), would be \(1 - \hat{y} = 0.745\). This illustrates a potential issue with the accuracy metric - a “high” accuracy may be reflective of the distribution of the target and not the model performance itself, so when comparing model accuracies, an appropriate baseline is needed.

We also consider the ROC-AUC metric (Receiving Operator Characteristic Area Under the Curve). Given the problem is one related to insurance, the modelling outcome of interest is not only to obtain the correct predictions (accuracy), but also accurate probabilities, which the AUC metric captures to some extent.

The AUC metric calculates the area under the ROC-curve, the plot of the true positive rate or sensitivity \(\frac{TP}{TP + FN}\) against the false positive rate \(\frac{FN}{TN + FP}\), assessing the performance of the classifier’s predicted probabilities across all possible decision thresholds. A classifier that always predicts 0s would result in a baseline AUC of \(0.5\).

As we do not know the specific threshold relevant to the insurance context of the problem, we will report both accuracy, with a decision threshold set to \(> 0.5\), and the AUC score for all models considered. In practice, the decision threshold would largely depend on the requirements of the modeller.

# train-test
X_train = as.matrix(df_train[, cont_feats])
y_train = as.numeric(as.matrix(df_train$target))
X_test = as.matrix(df_test[, cont_feats])
y_test = as.numeric(as.matrix(df_test$target))
# helper function for metrics
evaluate_metrics <- function(train_pred, test_pred, train_true, test_true){
  train_classes <- ifelse(train_pred > 0.5, 1,0)
  test_classes <- ifelse(test_pred > 0.5, 1,0)
  acc1 <- mean(train_classes == train_true)
  auc1 <- auc(roc(train_true, train_pred, quiet=TRUE))
  acc2 <- mean(test_classes == test_true)
  auc2 <- auc(roc(test_true, test_pred, quiet=TRUE))
  data.frame(train_acc=acc1, train_auc=auc1, test_acc = acc2, test_auc=auc2)
}
# metrics for naive prediction
results = data.frame(evaluate_metrics(rep(0, dim(df_train)[1]), 
                               rep(0, dim(df_test)[1]), 
                               df_train$target, df_test$target),
                     row.names=c("naive"))
results

Logistic Regression (SGD)

To fulfill the project requirements, we implement a `from scratch’ Stochastic Gradient Descent routine for Logistic Regression. The derivation follows (Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome 2009, 120–26)

Consider a matrix of variables \(X = (x_{1}, x_{2}, \ldots x_{n})^{T}\), where \(x_{i}\) denote the i-th row or observation, the target \(\mathbf{y} = (y_{1}, y_{2}, \ldots, y_{n})\). Let \(\frac{p(x_{i}; \beta)}{1 - p(x_{i} ; \beta)} = exp(\beta^{T} x_{i})\) denote the odds ratio.

We are interested in finding the coefficients \(\beta\), such that the logistic loss (also referred to as binary cross-entropy, and equivalent to the negative log-likelihood) is minimised. The logistic loss is given by:

\[\begin{align}l(\boldsymbol{\beta}) &= -\sum_{i = 1}^{N} y_{i} log(p(x_{i} ; \boldsymbol{\beta})) + (1 - y_{i}) log(1 - p(x_{i} ; \boldsymbol{\beta}))\\ &= \sum_{i = 1}^{N} \left [ y_{i}log \left (\frac{p(x_{i} ; \boldsymbol{\beta})}{1 - p(x_{i} ; \boldsymbol{\beta})} \right) + log(1 - p(x_{i} ; \boldsymbol{\beta})) \right ]\\ l(\boldsymbol{\beta}) &= -\sum_{i = 1}^{N}\left [y_{i} \boldsymbol{\beta}^{T} x_{i} - log(1 + exp(\boldsymbol{\beta}^{T}x_{i})) \right ] \tag{1} \end{align}\]

With the inclusion of a regularisation term, specifically a \(L^{2}\) penalty, the loss function becomes:

\[ \begin{equation}l(\boldsymbol{\beta}) = -\sum_{i = 1}^{N} \left [y_{i} \boldsymbol{\beta}^{T} x_{i} - log(1 + exp(\boldsymbol{\beta}^{T}x_{i})) \right ] - \lambda \boldsymbol{\beta}^{T} \boldsymbol{\beta} \tag{2} \end{equation}\]

The gradient of the loss function with respect to the coefficients \(\mathbf{\beta}\) is given by:

\[\nabla(\boldsymbol{\beta}) = -\sum_{i = 1}^{N} \left [ y_{i} x_{i} - \frac{x_{i}exp(\boldsymbol{\beta}^{T} x_{i})}{1 + exp(\boldsymbol{\beta}^{T} x_{i})} \right] - \lambda 2\boldsymbol{\beta}\]

We apply (stochastic) gradient descent. In short, this relies on updating the coefficients \(\beta\) iteratively based on a step size \(\lambda\):

\[\beta_{t + 1} = \beta_{t} - \lambda \nabla(\boldsymbol{\beta}_{t})\]

More sophisticated approaches such as Momentum or Adam can be used for the iterative updates; these are better outlined in (Nisheeth K. Vishnoi 2020). In our simple implementation, we use the Barzilai-Borwein method (Murphy, Kevin P. 2012, 444–45) to determine the step size.

\[\lambda_{t} = \frac{|(\beta_{t} - \beta_{t - 1})^{T}(\nabla F(\beta_{t}) - \nabla F(\beta_{t - 1}))|}{\| \nabla F(\beta_{t}) - \nabla F(\beta_{t - 1}))\|^{2}}\] Given sufficient iterations and appropriate step size, the model should converge (i.e. the change in the coefficients \(\|\beta_{t + 1} - \beta_{t}\|\) or the change in loss is less than some threshold \(\epsilon\)), although this is not guaranteed.

# binary crossentropy / log-loss
log_loss <- function(x, y, betas, lambda){
  logits <- x %*% betas
  - (t(y) %*% logits - sum(log(1 + exp(logits))) + lambda * t(betas) %*% betas) / dim(x)[1]
}
# logistic regression gradients
gradients <- function(x, y, betas, lambda){
  logits <- x %*% betas
  - (t(x) %*% (y - exp(logits)/(1 + exp(logits)))) - lambda *2 * betas / dim(x)[1]
}
# pre set parameters
p = dim(X_train)[2]
lambda = 0
n_iters <- 100
init_step_size <- 1e-6
set.seed(2021)
beta_init <- matrix(rnorm(p),nrow=p)
beta_path <- matrix(rep(0, n_iters * p), nrow = n_iters, ncol=p)
beta_path[1,] = beta_init
last_grad <- grad <- gradients(X_train, y_train, beta_path[1,], lambda)
beta_path[2,] = beta_init - init_step_size * grad
grad <- gradients(X_train, y_train, beta_path[2,], lambda)
losses <- rep(0, n_iters)
# iteratively update betas
for (i in 3:n_iters){
    step_size <- as.numeric(t(beta_path[i - 1,] - beta_path[i - 2,]) %*% (grad - last_grad) / 
                    (t(grad - last_grad) %*% (grad - last_grad)))
    beta_path[i,] <- beta_path[i - 1,] - step_size * grad
    last_grad <- grad
    grad <- gradients(X_train, y_train, beta_path[i, ], lambda)
    losses[i] <- log_loss(X_train, y_train, beta_path[i,], lambda)
}
# plot + results
ggplot(data.frame(step = 3:n_iters, loss=losses[3:n_iters])) + 
  geom_line(aes(x = step, y = loss)) +
  ggtitle("Binary Crossentropy vs. Iterations")

pred_train <- as.numeric(1 / (1 + exp(-X_train %*% beta_path[100,])))
pred_test <- as.numeric(1 / (1 + exp(-X_test %*% beta_path[100,])))
results["sgd",] <- evaluate_metrics(pred_train, pred_test, df_train$target, df_test$target)
results["sgd",]

We apply our “from scratch” implementation of Logistic Regression using SGD with all the numerical variables, obtaining a test accuracy of \(0.751\) and a test AUC of \(0.7082\), a small improvement in terms of accuracy from the naive strategy, but a larger improvement in terms of AUC.

Remark: We note that our implementation of Logistic Regression is quite limited, for example an inability to process the factor datatype (unless we append a one-hot encoded matrix), or an inability to examine coefficient standard errors (unless we change to a Hessian based approach / Newton’s method). Given the limitations of our implementation, we shall subsequently use the glm package for logistic regression, and the glmnet package for regularised logistic regression.

Logistic Regression (Few Predictors)

Having explored the efficacy of logistic regression on this modelling task, we now utilise a more robust implementation of logistic regression via the glm package. We consider a more parsimonious logistic regression model, hand-picking several features based on our exploratory data analysis. We select the features that have discernible differences in their distributions conditioned on the target: the predictors cont3, cont4, cat13, cat18.

By selecting few predictors, coupled with the in-built features of the glm implementation, we can obtain, to some extent, interpretability and understanding of the model. This will help guide our analysis, and identify any potential issues before expanding to more predictors or different models. We will also use this as a baseline to compare the performance of subsequent models with.

glm_base <- glm(target~cont3+cont4+cat13+cat18, data = df_train, family=binomial(link="logit"))
summary(glm_base)

Call:
glm(formula = target ~ cont3 + cont4 + cat13 + cat18, family = binomial(link = "logit"), 
    data = df_train)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.5707  -0.6702  -0.6021   0.3931   2.0095  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept)  -1.4821     1.0756  -1.378  0.16824    
cont3        -0.6554     0.2276  -2.880  0.00398 ** 
cont4        -0.4772     0.2063  -2.313  0.02070 *  
cat13B        1.8681     0.3346   5.584 2.36e-08 ***
cat18B        0.5581     1.0714   0.521  0.60243    
cat18C        2.6081     1.0816   2.411  0.01590 *  
cat18D        2.4342     1.0805   2.253  0.02427 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 3406.6  on 2999  degrees of freedom
Residual deviance: 2962.8  on 2993  degrees of freedom
AIC: 2976.8

Number of Fisher Scoring iterations: 4
vif(glm_base)
          GVIF Df GVIF^(1/(2*Df))
cont3 1.110907  1        1.053996
cont4 1.053938  1        1.026615
cat13 1.010521  1        1.005247
cat18 1.066127  3        1.010729
ggcoef(glm_base)

Firstly, the predictors are almost all statistically significant at a 5% level, with the exception of cat18B and the intercept. Inspecting the coefficient plots, which visualise the confidence intervals of each coefficient estimate, we observe that the confidence intervals of all variables except cat18B are significant at 5% level, and do not include 0. We also see that the width of the confidence intervals of the coefficients of cat18D and cat18C, which are proportional to their variances, are much larger than that of cat13B, cont4 and cont3, however as the categorical and continuous variables are on completely different scales, these are not directly comparable. It could be that this is a result of subsampling, and that as we increase the sample size the standard errors of the coefficients would decrease.

Secondly, we inspect the generalised variance inflation factor (GVIF). The variance inflation factor captures the extent to which the standard error of a predictor is increased due to its correlation with other predictors in the model, corrected by the number of degrees of freedom corresponding to the number of levels in the categorical variables. The GVIF of all predictors in our model are less than 2, which suggests there is no evidence of multicollinearity. Although our analysis of the variance inflation factor suggests that there is no multicollinearity, a quick inspection of the variable cat18 indicates that there are very few observations in the level cat18A; thus cat18B would be approximately close to 1 - cat18C - cat18D and potentially collinear when the intercept is considered.s This suggests that a more robust method to encode the categorical variables could be required, such as merging infrequent categories, or perhaps using regularisation instead.

table(df_train$cat18)

   A    B    C    D 
   8 2584  194  214 

Assuming that there is no multicollinearity, the coefficients for each predictor represents the association of that predictor with the log-odds of having target == 1: \(\log \left ( \frac{P(\text{target}=1)}{P\text{(target}=0)} \right )\).

The coefficients of the dummy variables indicate the average difference between the log odds of that factor level group compared to the baseline level group. In our regression, the first category (A) of each categorical variable is taken as the baseline group. For example, the coefficient of cat13B implies the following equation: \[\log\frac{P(target=1|cat13=B)}{P(target=0|cat13=B)}-\log\frac{P(target=1|cat13=A)}{P(target=0|cat13=A)}=1.8681\]

This suggests that claims with cat13=B have \(exp(1.8681)-1=5.48\) higher odds than claims with cat13=A. Given that the coefficient of cat18B is not statistically significant (or in other words, its standard error is high), this suggests that its association with target may not be significantly different from cat18=A (inspecting the conditional plot from our exploratory data analysis, their conditional distributions on the target appear to be similar). However, having cat18=C as opposed to having cat18=A is associated with a \(exp(2.6081)-1=12.6\) larger increase in the log-odds of having target==1. Similarly, claims with cat18=D are associated with a \(exp(2.4342)-1=9.4\) greater log-likelihood of having target==1.

To interpret the coefficients of the continuous variables, log odds need to be calculated using specific pairs of values of the predictors. The non-linearity of the logistic function means that a change of one unit in the value of a predictor is not the same across the range of the predictor. cont3 and cont4 have negative coefficients as expected, and are interpreted as follows. A one unit increase in cont3 is associated with an \(exp(-0.6554)=0.519\) multiplicative effect on the odds, i.e. a 48% decrease in odds compared to the previous odds. Similarly, a claim with a one unit higher value of ‘cont4’ is \(1 - exp(-0.4772)=37.9\%\) less likely to have target==1 than a claim with a one unit lower value of cont4. As the true range of each continuous variable before normalisation is unknown, it is unclear whether this associated change in target is considered large in reality.

pred_train <- predict(glm_base, df_train, type="response")
pred_test <- predict(glm_base, df_test, type="response")
results["glm-base",] <- evaluate_metrics(pred_train, pred_test, df_train$target, df_test$target)
results["glm-base",]

With just a few predictors, the baseline model has a test accuracy of \(80.3\%\), which is \(7.5\%\) higher than the test accuracy of the naive model. The test AUC of the baseline model is \(0.703\), which is 0.203 higher than that of the naive model, which is a fair improvement.

Logistic Regression (All Predictors)

We now run a logistic regression using all 31 predictors. The regression coefficients can be interpreted in a similar way as the baseline model. The regression output shows that all predictors are significant at 5%, but this is unreliable since the categorical variables have many levels (623 in total).

glm_full <- glm(target~., data=df_train, family=binomial(link="logit"), 
            control = list(maxit = 100))
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
pred_train <- predict(glm_full, df_train, type="response")
prediction from a rank-deficient fit may be misleading
glm_full$xlevels = lapply(df[,cat_feats], levels)
pred_test <- predict(glm_full, df_test, type="response")
prediction from a rank-deficient fit may be misleading
results["glm-full",] <- evaluate_metrics(pred_train, pred_test, df_train$target, df_test$target)
results["glm-full",]

The full model has a higher train accuracy (0.801) but lower test accuracy (0.59) than the baseline model, which implies overfitting. The test AUC is 0.543, which is much lower than the baseline. This is an example of the bias-variance trade-off; the more complex, full model with 31 predictors has a lower bias but higher variance than the simpler, baseline model with 4 predictors.

We also obtain an error: “prediction from a rank-deficient fit may be misleading” which suggests multicollinearity. In subsequent sections, we aim to overcome this to improve the stability of our model using shrinkage in a subsequent section.

Outlier Detection

At this stage, we suspect that some outliers might be heavily influencing the performance of our models, and hence we seek to detect outliers using a few measures. Formally, outliers are defined as observations with a response vector that is unusual conditional on covariates (predictors). Firstly, we look for points with large (studentised) residuals. We can then test if these residuals are significantly larger than those of other points by examining the Bonferroni-adjusted p-values. Ten points are identified to have large studentised residuals with adjust p-values less than 0.05. We store the indices of these points to be removed later.

outlierTest(glm_full)
outliers <- as.numeric(names(outlierTest(glm_full)$p))

Observations that are far from the average covariate pattern are considered to have high leverage and can be measured using hat values. Here, there are at least 100 points with high leverage. Finally, we also measure for influence, which is an observation that is an outlier and have high leverage. These are likely to influence the regression coefficients and influence can be thought of as the product of leverage and outlier. Here, we plot studentised residuals against hat-values with the size of a circle being proportional to the Cook’s distance of an observation- a measure of influence.

influenceIndexPlot(glm_full, vars = "hat")
Error in influenceIndexPlot(glm_full, vars = "hat") : 
  could not find function "influenceIndexPlot"

We observe that there are 4 observations with high influence. We remove these observations along with the points identified as outliers earlier (14 in total), and compare the performance of our updated model with the original model (see Appendix).

We find that removing outliers (see Appendix) leads to a higher test accuracy of 0.684 but a lower test AUC of 0.539. We cannot interpret this further without knowing the specific threshold used to classify the target in the data. Analysing the regression output, we see that leaving out the outliers does not change the coefficient estimates, and since we have no intuitive reason to believe that these outlier values are extreme (due to no knowledge about the variables themselves), we decided to keep these outlier data points for all other models. Furthermore, it is possible that these outliers may be less impactful as we increase the sample size.

Regularised Logistic Regression

Given the issue of high dimensionality, we consider a regularised form of logistic regression. Specifically, we consider ridge (logistic) regression, with the functional form as in equation (2) This method shrinks coefficients by imposing a penalty on their size, thereby reducing model complexity. We use the implementation offered by the glmnet package; in doing so we need to convert the data type into matrices.The glmnet package requires the data to be in a matrix data type, and hence we make the corresponding adjustment.

X_train = df_train[, -length(df_train)]
y_train <- df_train$target
X_test = df_test[, -length(df_test)]
y_test <- df_test$target
X_train = model.matrix(~., X_train)
X_test = model.matrix(~., X_test)
glm_reg <- cv.glmnet(X_train, y_train, 
                  family="binomial"(link="logit"), alpha=0)
pred_train <- as.numeric(predict(glm_reg, X_train, type="response"))
pred_test <- as.numeric(predict(glm_reg, X_test, type="response"))
results["glm-ridge",] <- evaluate_metrics(pred_train, pred_test, df_train$target, df_test$target)
results["glm-ridge",]

Random Forest

To improve performance, we draw on the usage of non-linear tree-based models, specifically random forests(Leo Breiman 2001). Intuitively, a random forest averages different decision trees (known as bagging) so as to reduce the variance of individual trees. This thus reduces the likelihood of individual trees overfitting to the training data.

rf_recipe <- recipe(target~., data = df_train)

rf_model <- 
  rand_forest(trees=100) %>%
  set_engine("ranger", importance="impurity", seed=2021) %>%
  set_mode("classification")

rf_workflow <- workflow() %>%
  add_recipe(rf_recipe) %>%
  add_model(rf_model)

rf1 <- fit(rf_workflow, df_train)
ranger_obj <- pull_workflow_fit(rf1)$fit
# plot feature importances
rf_feat_imp <- rownames_to_column(data.frame(ranger_obj$variable.importance));
names(rf_feat_imp) <- c("feat", "importance")
ggplot(rf_feat_imp, aes(x = reorder(feat, importance), y = importance))+ 
      geom_bar(stat="identity", position="dodge")+ coord_flip()+
      ylab("Feature Importance (Gini Impurity)")+
      xlab("Feature")+
      ggtitle("Random Forest Feature Importances")
#evaluate metrics
pred_train <- unlist(predict(rf1,df_train,type='prob')[,2])
pred_test <- unlist(predict(rf1, df_test, type='prob')[,2])
results["rf",] <- evaluate_metrics(pred_train, pred_test, df_train$target, df_test$target)
results["rf",]
temp <- function(x){partial(ranger_obj, train=df_train, pred.var = x, plot = TRUE, plot.engine = "ggplot2", paropts = list(.packages = "ranger"))}
plots <- lapply(names(df_train)[cont_feats], temp)
wrap_plots(plots)

The random forest has a test accuracy of 0.836 and a test AUC of 0.872, which is higher than the previous linear models. However, the train accuracy and AUC are significantly higher than the test performance, which suggest that there may be some degree of overfitting to the train data.

Random forests can be used to rank the importance of different features. Specifically, the x-axis is the Mean Decrease Accuracy, which reports how much accuracy the model loses when we exclude this variable. The more the accuracy falls by, the more important the particular variable is. Note here that for categorical variables, each level of the category is classified as a single variable. In this plot, we recorded the 30 most important variables.

We then run another random forest with the most important features. In doing so, we hope to reduce the degree of overfitting by reducing the complexity of the model. However, it does not make sense to drop some levels of a categorical variable while including the other levels. Hence, as long as a level is present in the top 30 features, we will include the entire category in our updated random forest model. This results in us keeping only 22 variables, from an initial 30.

Our reduced random forest has a slightly improved test accuracy and a slightly decreased test AUC. However, it does not solve the potential problem of overfitting as train performance is still significantly better than test performance. In fact, train performance on the reduced random forest is better than the random forest with a full set of variables - the reduced complexity of the model enabled it to have a lower bias on the training set.

XGBoost

For completeness, we also consider the xgboost library for gradient boosted decision trees. Gradient Boosted Decision Trees. The XGBoost package, introduced in (Chen, Tianqi and Guestrin, Carlos 2016) is a variant of Gradient Boosted Decision Trees (Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome 2009, 353–74). For Gradient Boosting Trees, each decision tree is trained sequentially on the residuals of the previous decision tree, which has the effect of reducing bias.

dmy_train <- dummyVars("~.", data = df_train[,-length(df_train)])
dmy_test <- dummyVars("~.", data = df_test[,-length(df_test)])
X_train <- as.matrix(data.frame(predict(dmy_train,df_train)))
X_test <- as.matrix(data.frame(predict(dmy_test,df_test)))
y_train = as.integer(as.matrix(df_train$target))
y_test = as.integer(as.matrix(df_test$target))
bst <- xgboost(data = X_train, label=y_train, max_depth = 2, nround = 10, 
               verbose=0,
               objective='binary:logistic',
               eval_metric="logloss")

pred_train <- predict(bst, X_train, type="response")
pred_test <- predict(bst, X_test, type="response")
results["xgb",] <- evaluate_metrics(pred_train, pred_test, df_train$target, df_test$target)
results["xgb",]

importance_matrix <- xgb.importance(model=bst)
xgb.plot.importance(importance_matrix)

# shapley values  
xgboost::xgb.ggplot.shap.summary(X_test, model = bst, 
                                 target_class = 1, top_n = 20)  # Summary plot

CatBoost

We also consider briefly examine the catboost library for gradient boosted decision trees, given its popularity on machine learning competitions such as Kaggle. The CatBoost library has the advantage of learning a target encoding, i.e. some numerical value for every category for each categorical variables. From an implementation point of view, this may reduce the preprocessing that may be required. For details on CatBoost, refer to (Prokhorenkova, Liudmila and Gusev, Gleb and Vorobev, Aleksandr and Dorogush, Anna Veronika and Gulin, Andrey 2017).

Again, the key parameters are the number of trees (iterations), and the depth of each tree. Due to computational limtations, we select arbitrary parameters iterations = 10 and depth = 8; these could be further fine-tuned using hyperparameter optimisation.

X_train = df_train[,c(cat_feats, cont_feats)]
y_train = as.integer(df_train$target)
X_test = df_test[,c(cat_feats, cont_feats)]
y_test = as.integer(df_test$target)

pool <- catboost.load_pool(X_train, y_train, cat_features = cat_feats)
model <- catboost.train(pool, params=list(depth = 8, iterations = 10, 
                                          loss_function='Logloss', verbose=0))

pred_train <- catboost.predict(model, catboost.load_pool(X_train), prediction_type = 'Probability')
pred_test <- catboost.predict(model, catboost.load_pool(X_test), prediction_type = 'Probability')

# feature importance
feat_importance <- catboost.get_feature_importance(model, pool)
importances <- data.frame(feat_importance[order(feat_importance, decreasing=FALSE),])
importances$features = rownames(importances)
names(importances) <- c("importance","features")
importances$features <- factor(importances$features, level=importances$features)
ggplot(importances, aes(x=importance, y=features)) + 
  geom_bar(stat="identity") +
  ggtitle("CatBoost Feature Importance")


#Shapley Values
data_shap_tree <- catboost.get_feature_importance(model, pool = pool,
                                                  type = "ShapValues")
data_shap_tree <- data.frame(data_shap_tree[, -ncol(data_shap_tree)]) 
names(data_shap_tree) = names(df[, c(cat_feats, cont_feats)])

# ggplot(stack(data_shap_tree), aes(x = ind, y = values)) +
#     geom_point(aes(color = values)) + coord_flip() + 
#     ggtitle("Shapley Values by variable")  + scale_color_viridis_c()

results["catboost",] <- evaluate_metrics(pred_train, pred_test, df_train$target, df_test$target)
results["catboost",]

We obtain a train accuracy of \(0.882\) and a train AUC of \(0.921\), versus a test accuracy of \(0.836\) and test AUC \(0.858\). This suggests that the model may be overfitting for our particular choice of paramters (and also for our subsampled dataset size).

For interpretability, we can use CatBoost’s feature importance. From the CatBoost documentation, CatBoost’s in-built and default feature importance does the following:

For each feature, PredictionValuesChange shows how much on average the prediction changes if the feature value changes. The bigger the value of the importance the bigger on average is the change to the prediction value, if this feature is changed.

We could also set the type of feature importance to Shapley Values, although we omit the plot in this case due to the lack of in-built support for plotting.

Results and Evaluation

results[order(results$test_auc, results$test_acc),]

Although the tree-based methods are less interpretable and more “black box” to some extent, we can make use of model interpretability techniques.

It could be that our resutls are not fully robust, given we have subsampled the original data.

Conclusion

In this project, we demonstrated the use of logistic regression, gradient descent, regularisation, random forest, gradient boosting and hyperparameter tuning techniques to achieve interpretability and model accuracy (in separate cases) in predicting the amount of an insurance claim. Model ___ was the model with highest AUC, although interpretability was sacrificed. On the other hand, our baseline model with only a few predictors is easy to interpret, however it has a relatively low AUC of 0.703. In practice, depending on whether the goals of the company lean towards prediction or explanation, an appropriate trade-off between model complexity and interpretability has to be chosen.

From the insurer’s profit perspective, understanding which characteristics are highly correlated with large amounts of claims could also be used to inform whether or not a particular insurance application is accepted, even if causality cannot be inferred. However, this might lead to some legal and/or ethical issues. For example, the UK’s 2010 Equality Act makes discriminating on nine protected characteristics, including age, gender, race and religion, illegal (Pattni, 2020). Therefore, proper data governance needs to be maintained and privacy impact assessments need to be conducted before any machine learning models of this kind are deployed in industry (GDPR, 2018).

Bibliography

Chen, Tianqi and Guestrin, Carlos. 2016. “XGBoost: A Scalable Tree Boosting System.” https://arxiv.org/pdf/1603.02754.pdf.
Efron, Bradley and Hastie, Trevor. 2016. Computer Age Statistical Inference. Cambridge University Press.
Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome. 2009. The Elements of Statistical Learning: Data Mining, Inference, and Prediction. Springer.
James, Gareth and Witten, Daniela and Hastie, Trevor and Robert Tibshirani. 2017. An Introduction To Statistical Learning. Springer.
Leo Breiman. 2001. “Random Forests.” https://link.springer.com/content/pdf/10.1023/A:1010933404324.pdf.
Molnar, Christoph. 2019. Interpretable Machine Learning. “https://christophm.github.io/interpretable-ml-book”.
Murphy, Kevin P. 2012. Machine Learning: A Probabilistic Perspective. MIT Press. https://christophm.github.io/interpretable-ml-book.
Nisheeth K. Vishnoi. 2020. Algorithms for Convex Optimization. https://convex-optimization.github.io/.
Parr, Terence and Howard, Jeremy. 2019. The Mechanics of Machine Learning. https://mlbook.explained.ai/.
Prokhorenkova, Liudmila and Gusev, Gleb and Vorobev, Aleksandr and Dorogush, Anna Veronika and Gulin, Andrey. 2017. “CatBoost: Unbiased Boosting With Categorical Features.” https://arxiv.org/pdf/1706.09516.pdf.

Appendix

Comparison of Coefficients with Outliers Removed

influencers <- as.numeric(rownames(influencePlot(glm_full)))
glm_full_influencers <- update(glm_full, subset = c(-influencers))
glm_full_outliers <- update(glm_full, subset = c(-outliers))
removal_list <- union(outliers, influencers)
glm_full_removed <- update(glm_full, subset = c(-removal_list))
compareCoefs(glm_full, glm_full_influencers, glm_full_outliers, glm_full_removed)
set.seed(2021)
cv_splits <- rsample::vfold_cv(df_train, strata = target, v= 3)
mod <- logistic_reg(penalty = tune(),
                    mixture = tune()) %>%
  set_engine("glmnet")

glmnet_recipe <- recipe(target~.,data = df_train) %>% 
  step_dummy(all_nominal(), -all_outcomes())

glmnet_workflow<- workflow() %>%
  add_recipe(glmnet_recipe) %>%
  add_model(mod)

glmn_set <- parameters(penalty(range = c(-5,1), trans = log10_trans()),
                       mixture())

glmn_grid <- 
  grid_regular(glmn_set, levels = c(7, 5))
ctrl <- control_grid(save_pred = TRUE, verbose = TRUE)

glmn_tune <- 
  tune_grid(glmnet_workflow,
            resamples = cv_splits,
            grid = glmn_grid,
            metrics = metric_set(roc_auc),
            control = ctrl)


best_glmn <- select_best(glmn_tune, metric = "roc_auc")

glmnet_model <- 
  logistic_reg() %>%
  set_engine("glmnet", seed=2021) %>%
  set_mode("classification")



glm_reg <- fit(glmnet_workflow, data = df_train)
xgb_spec <- boost_tree(
  trees = 100, 
  tree_depth = tune(),
  learn_rate = tune()                         ## step size
) %>% 
  set_engine("xgboost") %>% 
  set_mode("classification")

xgb_grid <- grid_latin_hypercube(
  tree_depth(),
  learn_rate(),
  size = 5
)

xgb_wf <- workflow() %>%
  add_formula(target ~ .) %>%
  add_model(xgb_spec)

set.seed(123)
vb_folds <- vfold_cv(df_train, strata = target)

doParallel::registerDoParallel()

set.seed(234)
xgb_res <- tune_grid(
  xgb_wf,
  resamples = vb_folds,
  grid = xgb_grid,
  control = control_grid(save_pred = TRUE)
)
best_auc <- select_best(xgb_res, "roc_auc")
final_xgb <- finalize_workflow(
  xgb_wf,
  best_auc
)
library(vip)

final_xgb %>%
  fit(data = vb_train) %>%
  pull_workflow_fit() %>%
  vip(geom = "point")

final_res <- last_fit(final_xgb, df_split)

collect_metrics(final_res)

xgb_res %>%
  collect_metrics() %>%
  filter(.metric == "roc_auc") %>%
  select(mean, mtry:sample_size) %>%
  pivot_longer(mtry:sample_size,
               values_to = "value",
               names_to = "parameter"
  ) %>%
  ggplot(aes(value, mean, color = parameter)) +
  geom_point(alpha = 0.8, show.legend = FALSE) +
  facet_wrap(~parameter, scales = "free_x") +
  labs(x = NULL, y = "AUC")
"https://curso-r.github.io/treesnip/index.html"
library(treesnip)
LS0tCnRpdGxlOiAiU1QzMTAgQ291cnNlIFByb2plY3QiCmF1dGhvcjogIkNhbmRpZGF0ZSBOdW1iZXJzOiAxMDkzNiwgMTI4MzYsIDE0MzU5IgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZC8lbS8leScpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBrZWVwX21kOiB0cnVlCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CmhlYWRlci1pbmNsdWRlczoKLSBcdXNlcGFja2FnZSB7aHlwZXJyZWZ9Ci0gXGh5cGVyc2V0dXAge2NvbG9ybGlua3MgPSB0cnVlLCBsaW5rY29sb3IgPSBibHVlLCB1cmxjb2xvciA9IGJsdWV9CnJlZmVyZW5jZXM6Ci0gaWQ6IGhhc3RpZTIwMDllbGVtZW50cwogIHRpdGxlOiAnVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nOiBEYXRhIE1pbmluZywgSW5mZXJlbmNlLCBhbmQgUHJlZGljdGlvbicKICBhdXRob3I6ICJIYXN0aWUsIFRyZXZvciBhbmQgVGlic2hpcmFuaSwgUm9iZXJ0IGFuZCBGcmllZG1hbiwgSmVyb21lIgogIHB1Ymxpc2hlcjogU3ByaW5nZXIKICB0eXBlOiBib29rCiAgaXNzdWVkOgogICAgeWVhcjogMjAwOQotIGlkOiBqYW1lczIwMTdpbnRyb2R1Y3Rpb24KICB0aXRsZTogIkFuIEludHJvZHVjdGlvbiBUbyBTdGF0aXN0aWNhbCBMZWFybmluZyIKICBhdXRob3I6ICJKYW1lcywgR2FyZXRoIGFuZCBXaXR0ZW4sIERhbmllbGEgYW5kIEhhc3RpZSwgVHJldm9yIGFuZCBSb2JlcnQgVGlic2hpcmFuaSIKICBwdWJsaXNoZXI6IFNwcmluZ2VyCiAgdHlwZTogYm9vawogIGlzc3VlZDoKICAgIHllYXI6IDIwMTcKLSBpZDogZWZyb24yMDE2Y29tcHV0ZXIKICB0aXRsZTogIkNvbXB1dGVyIEFnZSBTdGF0aXN0aWNhbCBJbmZlcmVuY2UiCiAgYXV0aG9yOiAiRWZyb24sIEJyYWRsZXkgYW5kIEhhc3RpZSwgVHJldm9yIgogIHB1Ymxpc2hlcjogQ2FtYnJpZGdlIFVuaXZlcnNpdHkgUHJlc3MKICB0eXBlOiBib29rCiAgaXNzdWVkOgogICAgeWVhcjogMjAxNgotIGlkOiBtb2xuYXIyMDE5CiAgdGl0bGU6ICJJbnRlcnByZXRhYmxlIE1hY2hpbmUgTGVhcm5pbmciCiAgYXV0aG9yOiAiTW9sbmFyLCBDaHJpc3RvcGgiCiAgdHlwZTogYm9vawogIGlzc3VlZDoKICAgIHllYXI6IDIwMTkKICBVUkw6IOKAnGh0dHBzOi8vY2hyaXN0b3BobS5naXRodWIuaW8vaW50ZXJwcmV0YWJsZS1tbC1ib29r4oCdCi0gaWQ6IG11cnBoeTIwMTJtYWNoaW5lCiAgdGl0bGU6ICJNYWNoaW5lIExlYXJuaW5nOiBBIFByb2JhYmlsaXN0aWMgUGVyc3BlY3RpdmUiCiAgYXV0aG9yOiAiTXVycGh5LCBLZXZpbiBQLiIKICBwdWJsaXNoZXI6IE1JVCBQcmVzcwogIHR5cGU6IGJvb2sKICBpc3N1ZWQ6CiAgICB5ZWFyOiAyMDEyCiAgVVJMOiAiaHR0cHM6Ly9jaHJpc3RvcGhtLmdpdGh1Yi5pby9pbnRlcnByZXRhYmxlLW1sLWJvb2siCi0gaWQ6IHByb2tob3JlbmtvdmEyMDE3Y2F0Ym9vc3QKICB0aXRsZTogIkNhdEJvb3N0OiBVbmJpYXNlZCBCb29zdGluZyBXaXRoIENhdGVnb3JpY2FsIEZlYXR1cmVzIgogIHR5cGU6IGFydGljbGUtam91cm5hbAogIGF1dGhvcjogIlByb2tob3JlbmtvdmEsIExpdWRtaWxhIGFuZCBHdXNldiwgR2xlYiBhbmQgVm9yb2JldiwgQWxla3NhbmRyIGFuZCBEb3JvZ3VzaCwgQW5uYSBWZXJvbmlrYSBhbmQgR3VsaW4sIEFuZHJleSIKICBpc3N1ZWQ6CiAgICB5ZWFyOiAyMDE3CiAgVVJMOiAiaHR0cHM6Ly9hcnhpdi5vcmcvcGRmLzE3MDYuMDk1MTYucGRmIgotIGlkOiBjaGVuMjAxNnhnYm9vc3QKICB0aXRsZTogIlhHQm9vc3Q6IEEgU2NhbGFibGUgVHJlZSBCb29zdGluZyBTeXN0ZW0iCiAgYXV0aG9yOiAiQ2hlbiwgVGlhbnFpIGFuZCBHdWVzdHJpbiwgQ2FybG9zIgogIHR5cGU6IGFydGljbGUtam91cm5hbAogIGlzc3VlZDoKICAgIHllYXI6IDIwMTYKICBVUkw6ICJodHRwczovL2FyeGl2Lm9yZy9wZGYvMTYwMy4wMjc1NC5wZGYiCi0gaWQ6IHBhcnIyMDE5bW9tbAogIHRpdGxlOiAiVGhlIE1lY2hhbmljcyBvZiBNYWNoaW5lIExlYXJuaW5nIgogIGF1dGhvcjogIlBhcnIsIFRlcmVuY2UgYW5kIEhvd2FyZCwgSmVyZW15IgogIHR5cGU6IGJvb2sKICBpc3N1ZWQ6CiAgICB5ZWFyOiAyMDE5CiAgVVJMOiAiaHR0cHM6Ly9tbGJvb2suZXhwbGFpbmVkLmFpLyIKLSBpZDogdmlzaG5vaTIwMjBvcHRpbWlzYXRpb24KICB0aXRsZTogIkFsZ29yaXRobXMgZm9yIENvbnZleCBPcHRpbWl6YXRpb24iCiAgYXV0aG9yOiAiTmlzaGVldGggSy4gVmlzaG5vaSIKICB0eXBlOiBib29rCiAgaXNzdWVkOgogICAgeWVhcjogMjAyMAogIFVSTDogImh0dHBzOi8vY29udmV4LW9wdGltaXphdGlvbi5naXRodWIuaW8vIgotIGlkOiBicmVpbWFuMjAxNnJhbmRvbWZvcmVzdHMKICB0aXRsZTogIlJhbmRvbSBGb3Jlc3RzIgogIGF1dGhvcjogTGVvIEJyZWltYW4KICBpc3N1ZWQ6CiAgICB5ZWFyOiAyMDAxCiAgVVJMOiAiaHR0cHM6Ly9saW5rLnNwcmluZ2VyLmNvbS9jb250ZW50L3BkZi8xMC4xMDIzL0E6MTAxMDkzMzQwNDMyNC5wZGYiCi0tLQoKIyBTVDMxMCBDb3Vyc2UgUHJvamVjdAoKVGhlIGFpbSBvZiB0aGlzIHByb2plY3QgaXMgdG8gZGVtb25zdHJhdGUgdGhlIGFwcGxpY2F0aW9uIG9mIG1hY2hpbmUgbGVhcm5pbmcgdGVjaG5pcXVlcyBkaXNjdXNzZWQgaW4gdGhlIFNUMzEwIE1hY2hpbmUgTGVhcm5pbmcgbW9kdWxlLCBpbiB0dXJuIGRyYXdpbmcgcHJpbWFyaWx5IGZyb20gdGhlIGV4cG9zaXRpb24gaW4gW0BqYW1lczIwMTdpbnRyb2R1Y3Rpb25dIGFuZCBbQGhhc3RpZTIwMDllbGVtZW50c10gYW1vbmcgb3RoZXIgdGV4dHMuIFdlIGV4YW1pbmUgdGhlIHVzZSBvZiBib3RoIGxpbmVhciB0ZWNobmlxdWVzLCBlLmcuICBMb2dpc3RpYyBSZWdyZXNzaW9uLCAgYW5kIG5vbi1saW5lYXIgbWV0aG9kcywgZS5nLiBSYW5kb20gRm9yZXN0cyBhbmQgR3JhZGllbnQgQm9vc3RlZCBEZWNpc2lvbiBUcmVlcywgZm9yIGEgZ2VuZXJpYyBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbS4KIAoqKlJlbWFyayoqOiBUaGlzIHJlcG9ydCBpcyBiZXN0IHZpZXdlZCBhcyBhIGAuaHRtbGAgZmlsZS4gSWYgZXhlY3V0aW5nIHRoaXMgbm90ZWJvb2tzIGFzIGEgYC5SbWRgIGZpbGUsIGVuc3VyZSB0aGF0IGFsbCBsaWJyYXJpZXMgYW5kIGRlcGVuZGVuY2llcyBhcmUgaW5zdGFsbGVkIGFuZCB0aGF0IHRoZSBjaHVua3MgYXJlIGV4ZWN1dGVkIGluIHNlcXVlbnRpYWwgb3JkZXIuCgogPCEtLSBUaGlzIG5vdGVib29rIGlzIGNvbG91ci1ibGluZCBmcmllbmRseSB0aHJvdWdoIHRoZSB1c2Ugb2YgdGhlIGRlZmF1bHQsIGdyYXlzY2FsZSwgdmlyaWRpcyBjb2xvciBzY2FsZXMgZm9yIGNoYXJ0cyAoc2VlIFtodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2Uvc2NhbGVfY29sb3VyX2NvbnRpbnVvdXMuaHRtbD9xPWNvbG9yJTIwYmxpbmQjYXJndW1lbnRzXSgjaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3NjYWxlX2NvbG91cl9jb250aW51b3VzLmh0bWw/cT1jb2xvciUyMGJsaW5kI2FyZ3VtZW50cykpLiAtLT4KCgojIyBEYXRhc2V0IAoKV2Ugb2J0YWluIHRoZSBkYXRhc2V0IGZyb20gdGhlIFtLYWdnbGUgTWFyY2ggVGFidWxhciBQbGF5Z3JvdW5kXSgjaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jL3RhYnVsYXItcGxheWdyb3VuZC1zZXJpZXMtbWFyLTIwMjEvb3ZlcnZpZXcpIGNvbXBldGl0aW9uLiBUaGUgZGF0YSBjb25zaXN0cyBvZiAqYW5vbnltaXNlZCBmZWF0dXJlcyosIHdoaWNoIGNvcnJlc3BvbmQgdG8gYSBiaW5hcnkgb3V0Y29tZSB2YXJpYWJsZTsgaW4gb3RoZXIgd29yZHMgdGhlIHRhc2sgaXMgYSAqKmNsYXNzaWZpY2F0aW9uIHByb2JsZW0qKi4gQWx0aG91Z2ggdGhlIGRhdGEgaXMgYW5vbnltaXNlZCwgdGhlIGNoYWxsZW5nZSBkZXNjcmlwdGlvbiBzdGF0ZXM6Cgo+IFRoZSBkYXRhc2V0IHVzZWQgZm9yIHRoaXMgY29tcGV0aXRpb24gaXMgc3ludGhldGljIGJ1dCBiYXNlZCBvbiBhIHJlYWwgZGF0YXNldCBhbmQgZ2VuZXJhdGVkIHVzaW5nIGEgQ1RHQU4uIFRoZSBvcmlnaW5hbCBkYXRhc2V0IGRlYWxzIHdpdGggcHJlZGljdGluZyB0aGUgYW1vdW50IG9mIGFuIGluc3VyYW5jZSBjbGFpbS4gQWx0aG91Z2ggdGhlIGZlYXR1cmVzIGFyZSBhbm9ueW1pemVkLCB0aGV5IGhhdmUgcHJvcGVydGllcyByZWxhdGluZyB0byByZWFsLXdvcmxkIGZlYXR1cmVzLgoKV2UgYmVsaWV2ZSB0aGF0IHRoaXMgbWFjaGluZSBsZWFybmluZyB0YXNrIGlzIHdlbGwtbW90aXZhdGVkLCBhcyBwcmVkaWN0aW5nIHRoZSBhbW91bnQgb2YgYW4gaW5zdXJhbmNlIGNsYWltIG9yIHdoZXRoZXIgYW4gaW5zdXJhbmNlIGNsYWltIG9jY3VycmVkLCB3b3VsZCBoYXZlIGEgbWVhbmluZ2Z1bCBidXNpbmVzcyBpbXBhY3QgaW4gdGhlIGNvbnRleHQgb2YgYWN0dWFyaWFsIHByaWNpbmcgYW5kIHJpc2sgbWFuYWdlbWVudC4KCmBgYHtyIGxvYWQtbGlicywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyBnZW5lcmljIGZ1bmN0aW9ucyBhbmQgcGxvdHRpbmcKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dwbG90MikgCmxpYnJhcnkoR0dhbGx5KSAKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShkb1BhcmFsbGVsKQpsaWJyYXJ5KGZvcmVhY2gpCiMgbW9kZWxsaW5nCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkodGlkeW1vZGVscykKbGlicmFyeShnbG1uZXQpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KHhnYm9vc3QpCmxpYnJhcnkoY2F0Ym9vc3QpCiMgbWV0cmljcwpsaWJyYXJ5KGNhcikgI291dGxpZXJzCmxpYnJhcnkocFJPQykKbGlicmFyeShwZHApCiMgcmVhZCBpbiBkYXRhCmRmIDwtIHJlYWQuY3N2KGZpbGUgPSAiLi4vZGF0YS90cmFpbi5jc3YiKQpjYXQoYygiVGhlIGRpbWVuc2lvbnMgb2YgdGhlIGFycmF5IGFyZSA6IiwgZGltKGRmKVsxXSwgIiwgIiwgZGltKGRmKVsyXSkpCmBgYApUaGUgZGF0YXNldCBjb25zaXN0cyBvZiAzMCBmZWF0dXJlcywgIDE5ICpjYXRlZ29yaWNhbCB2YXJpYWJsZXMqIGFuZCAxMSBudW1lcmljYWwgdmFyaWFibGVzLCBhbmQgYSBiaW5hcnkgb3V0Y29tZSB2YXJpYWJsZSBgdGFyZ2V0YC4gV2Ugd2lsbCBuZWVkIHRvIHByb2Nlc3MgdGhlc2UgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGluIHNvbWUgbWFubmVyLCB3aGljaCB3ZSBzaGFsbCBjb25zaWRlciBpbiB0aGUgW3N1YnNlcXVlbnQgc2VjdGlvbl0oI3ByZXByb2Nlc3NpbmcpLgoKIyMgUHJlcHJvY2Vzc2luZwpXZSByZW1vdmUgdGhlIGZpcnN0IGNvbHVtbiBgaWRgLCB3aGljaCByZXByZXNlbnRzIGFuIHVuaXF1ZSBpZGVudGlmaWVyIGZvciBlYWNoIG9ic2VydmF0aW9uLiBXZSB0aGVuIGNvbnZlcnQgdGhlIGZpcnN0IDE5IGNvbHVtbnMsIHdoaWNoIGFyZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW50byB0aGUgKmZhY3RvciogZGF0YSB0eXBlIGluIFIuIEdpdmVuIGNvbXB1dGF0aW9uYWwgbGltaXRhdGlvbnMgKHRoZSBjb2RlIGlzIGV4ZWN1dGVkIG9uIHNpbmdsZSBDUFUgbGFwdG9wcyksIHdlIHN1YnNhbXBsZSBgbl90cmFpbiA9IDQwMDBgIG9ic2VydmF0aW9ucyBmcm9tIHRoZSBjb21wbGV0ZSBkYXRhc2V0IG9mIDMwMCwwMDAgb2JzZXJ2YXRpb25zLiBGb3Igb3VyIHN1YnNlcXVlbnQgYW5hbHlzaXMsIHdlIHdpbGwgdXNlIG9ubHkgdGhpcyBzdWJzYW1wbGUgb2YgNDAwMCBvYnNlcnZhdGlvbnMsIGFsdGhvdWdoIHRoZSBhcHByb2FjaGVzIGNhbiBiZSBuYXR1cmFsbHkgZXh0ZW5kZWQgdG8gYSBsYXJnZXIgZGF0YXNldCBnaXZlbiBncmVhdGVyIGNvbXB1dGF0aW9uYWwgcmVzb3VyY2VzLiAgV2UgZnVydGhlciBwYXJ0aXRpb24gdGhlIDQwMDAgb2JzZXJ2YXRpb25zLCBpbnRvIGEgdHJhaW4gc2V0IG9mIDMwMDAgb2JzZXJ2YXRpb25zIGFuZCBhIHRlc3Qgc2V0IG9mICAxMDAwIG9ic2VydmF0aW9ucy4gQWxsIHRyYWluaW5nIGFuZCB0dW5pbmcgYXJlIHBlcmZvcm1lZCBvbiB0aGUgdHJhaW5pbmcgZGF0YSwgYW5kIHdlIHdpbGwgY29uc2lkZXIgbW9kZWwgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Qgc2V0IHBlcmZvcm1hbmNlIHNjb3Jlcy4KCioqUmVtYXJrKio6IERpZmZlcmVudCBtb2RlbHMgd291bGQgcmVxdWlyZSBkaWZmZXJlbnQgcHJlcHJvY2Vzc2luZyBmb3IgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcywgYW5kIHRodXMgd2Ugd2lsbCBmdXJ0aGVyIGFkZHJlc3MgaW4gc3Vic2VxdWVudCBzZWN0aW9uczsgZm9yIGV4YW1wbGUgdGhlIGBnbG1gIHBhY2thZ2UgY2FuIGFjY2VwdCAqZmFjdG9ycyogYXMgYSBkYXRhIHR5cGUsIHdoZXJlYXMgZm9yIGBnbG1uZXRgIHRoZXkgbXVzdCBiZSBjb252ZXJ0ZWQgdG8gdGhlIG1vZGVsLm1hdHJpeCBmb3JtYXQuCgoqKlJlbWFyayoqOiBXaGVuIGVuY29kaW5nIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYXMgZHVtbXkgdmFyaWFibGVzIGluIG91ciBzdWJzZXF1ZW50IGFuYWx5c2lzLCB3ZSBmaW5kIHRoYXQgdGhlcmUgYXJlIG92ZXIgNjAwIHVuaXF1ZSBjYXRlZ29yaWVzIGFjcm9zcyBhbGwgMTkgb2YgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gIFdlIGNvdWxkIGFsc28gY29uc2lkZXIgZGlmZmVyZW50IG1ldGhvZHMgdG8gcHJvY2VzcyBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIHN1Y2ggYXMgbWVyZ2luZyBsZXNzIGZyZXF1ZW50bHkgb2NjdXJyaW5nIGNhdGVnb3JpZXMsIG9yICAqdGFyZ2V0IGVuY29kaW5nKiBbQHBhcnIyMDE5bW9tbCwgY2guIDZdLiBOb25ldGhlbGVzcywgd2Ugc3RpY2sgd2l0aCB0aGUgZmFjdG9ycyBhbmQgZHVtbWllcyBhcHByb2FjaCBnaXZlbiBvdXIgbGFjayBvZiBmYW1pbGlhcml0eSB3aXRoIHRoZXNlIG90aGVyIG1ldGhvZHMuCgojIyMgRmVhdHVyZSBFbmdpbmVlcmluZwoKQSBjaGFsbGVuZ2UgaW4gdGhpcyBjYXNlIGlzIHRoYXQgd2UgaGF2ZSBubyAqc3BlY2lmaWMqIGRvbWFpbiBrbm93bGVkZ2UgYWJvdXQgdGhlIG1lYW5pbmcgb2YgcGFydGljdWxhciBmZWF0dXJlcywgYW5kIHdoZXRoZXIgdGhleSBtYXkgYmUgdXNlZnVsLiBUaHVzIGluIHRoaXMgcHJvYmxlbSwgYW55IHByZXByb2Nlc3NpbmcgaW4gdGVybXMgb2YgZmVhdHVyZSBlbmdpbmVlcmluZywgYW5kIGEgcHJpb3JpIGZlYXR1cmUgc2VsZWN0aW9uIG1heSBub3QgYmUgdGhhdCBtZWFuaW5nZnVsLiBOb25ldGhlbGVzcywgd2UgY2FuIHJlc29ydCB0byBtb2RlbCBpbnRlcnByZXRhYmlsaXR5IG1ldGhvZHMgW0Btb2xuYXIyMDE5XSB0byBvYnRhaW4gYW4gZXgtcG9zdCBleHBsYW5hdGlvbiBvZiBvdXIgbW9kZWxzLiBJbiB0aGUgbGluZWFyIGNhc2UsIHdlIGNhbiBleGFtaW5lIGNvZWZmaWNpZW50cyBhbmQgdGhlaXIgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlLCBhbmQgaW4gdGhlIGNhc2Ugb2YgdHJlZS1iYXNlZCBtb2RlbHMsIHN1Y2ggYXMgRmVhdHVyZSBJbXBvcnRhbmNlcywgUGFydGlhbCBEZXBlbmRlbmNlIFBsb3RzLCBhbmQgU2hhcGxleSB2YWx1ZXMuCgpgYGB7ciBwYXJ0aXRpb259CmNhdF9mZWF0cyA9IDE6MTkgIyB0aGUgZmlyc3QgMTkgY29sdW1ucyBhcmUgY2F0ZWdvcmljYWwgCmNvbnRfZmVhdHMgPC0gMjA6MzAgIyBhbmQgdGhlIG5leHQgMTEgYXJlIGNvbnRpbnVvdXMKdGFyZ2V0X2NvbCA8LSAzMSAjIHRhcmdldAojIGNvbnZlcnQgY2F0cyB0byBmYWN0b3JzCmRmIDwtIGNvbHVtbl90b19yb3duYW1lcyhkZiwgdmFyID0gImlkIikgJT4lIAogICAgICAgICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLGFzLmZhY3RvcikgJT4lIAogICAgICAgICBtdXRhdGVfYXQodmFycyh0YXJnZXQpLCBmYWN0b3IpCiMgc3Vic2FtcGxlIHRoZSBkYXRhIGZvciBmYXN0ZXIgbW9kZWwgaW5mZXJlbmNlCnNldC5zZWVkKDEpCm5fdHJhaW4gPC0gNDAwMApkZl9zYW1wbGUgPSBkZltzYW1wbGUoMTpucm93KGRmKSwgbl90cmFpbiksXQojIFBhcnRpdGlvbiBkYXRhIGludG8gdHJhaW4gYW5kIHRlc3Q7IHRlc3Qgd2lsbCBiZSBvdXIgb29zIGRhdGEKc2V0LnNlZWQoMSkKZGZfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChkZl9zYW1wbGUsIHByb3AgPSAzLzQpCmRmX3RyYWluIDwtIHRyYWluaW5nKGRmX3NwbGl0KQpkZl90ZXN0IDwtIHRlc3RpbmcoZGZfc3BsaXQpCmBgYAoKIyMgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcwoKV2UgZmlyc3QgY29uZHVjdCBzb21lICpleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzKiBieSB2aXN1YWxpc2luZyB0aGUgdW5pdmFyaWF0ZSBhbmQgYml2YXJpYXRlIHJlbGF0aW9uc2hpcHMgaW4gdGhlIGRhdGFzZXQuIFRoaXMgYWxsb3dzIHVzIHRvIGdhaW4gc29tZSB1bmRlcnN0YW5kaW5nIG9mIHRoZSBkYXRhIGFuZCBwb3RlbnRpYWwgbW9kZWxsaW5nIGFwcHJvYWNoZXMuCgojIyMgVW5pdmFyaWF0ZSBFREEKV2Ugb2JzZXJ2ZSB0aGF0IHRoZSB1bml2YXJpYXRlIGRpc3RyaWJ1dGlvbnMgb2YgdGhlICpjb250aW51b3VzKiB2YXJpYWJsZXMgYXJlIGFsbCBtdWx0aS1tb2RhbCBhbmQgbm9uLW5vcm1hbCwgYnV0IHRoZXkgYXJlIGFsbCBub3JtYWxpc2VkIChzY2FsZWQpIHRvIHRoZSByYW5nZSBvZiAkWzAsIDFdJC4KYGBge3IgY29udC12aXp9CiMgZmxhdHRlbiBkZiBpbnRvIHVzaW5nIHBpdm90X2xvbmdlciBhbmQgcGxvdCBkaXN0cmlidXRpb24KZGYgJT4lIHBpdm90X2xvbmdlcihjb2xzID0gc3RhcnRzX3dpdGgoImNvbnQiKSwgbmFtZXNfdG8gID0gImNvbnQiKSAlPiUgCiAgIGdncGxvdChhZXMoeCA9IHZhbHVlKSkrCiAgIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAsIGFscGhhID0gMC44NSkrCiAgIGdndGl0bGUoIkNvbnRpbnVvdXMgZmVhdHVyZXMgZGlzdHJpYnV0aW9uIikrCiAgIGZhY2V0X3dyYXAoY29udH4uLCBzY2FsZXMgPSAiZnJlZSIpICsKICAgdGhlbWVfbWluaW1hbCgpCmBgYApGcm9tIHRoZSBkaXN0cmlidXRpb25zIG9mIGNhdGVnb3JpY2FsIHZhcmlhYmxlcywgd2Ugc2VlIHRoYXQgdGhlcmUgYXJlIHZhcmlhYmxlcyB3aXRoIHN1YnN0YW50aWFsbHkgbW9yZSBvYnNlcnZhdGlvbnMgaW4gb25lIGNhdGVnb3J5LCBhbmQgYWxzbyB2YXJpYWJsZXMgd2l0aCBhIGhpZ2ggbnVtYmVyIG9mICgkPjUwJCkgY2F0ZWdvcmllcywgd2hpY2ggd2lsbCBiZSBhbiBpc3N1ZSB0byBhZGRyZXNzIGZvciBhbnkgc3Vic2VxdWVudCBwcmVwcm9jZXNzaW5nLiAKYGBge3IgY2F0LXZpeiwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTE1fQojIGZsYXR0ZW4gZGYgaW50byB1c2luZyBwaXZvdF9sb25nZXIgYW5kIHBsb3QgZGlzdHJpYnV0aW9uCmRmICU+JSBwaXZvdF9sb25nZXIoY29scyA9IGNvbnRhaW5zKGMoImNhdCIsICJ0YXJnZXQiKSksIG5hbWVzX3RvICA9ICJjYXQiKSAlPiUgCiAgIGdncGxvdChhZXMoeCA9IHZhbHVlKSkrCiAgIGdlb21fYmFyKGFscGhhID0gMC44NSkrCiAgIGdndGl0bGUoIkNhdGVnb3JpY2FsIGZlYXR1cmVzIGRpc3RyaWJ1dGlvbiIpKwogICBmYWNldF93cmFwKGNhdH4uLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSA0KSArCiAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMzApCmBgYAoKCgojIyMgQml2YXJpYXRlIEVEQQoKV2UgZ3JvdXAgdGhlIGNvbnRpbnVvdXMgdmFyaWFibGVzIGJ5IHRoZSB0YXJnZXQgYW5kIHBsb3QgdGhlbSBhcyBib3hwbG90cyB0byBjaGVjayBmb3IgYW55IG9idmlvdXMgZGlmZmVyZW5jZXMgZGlzY2VybmlibGUgYnkgZXllLiBGcm9tIHRoZSBwbG90cywgY2xhaW1zIHdpdGggYHRhcmdldCA9PSAxYCBoYXZlIGxvd2VyIHZhbHVlcyBvZiBgY29udDNgIG9uIGF2ZXJhZ2UgKG1lZGlhbikgdGhhbiBjbGFpbXMgd2l0aCBgdGFyZ2V0ID09IDBgLiBDbGFpbXMgd2l0aCB0YXJnZXQ9MSBhbHNvIGhhdmUgYSBtdWNoIGhpZ2hlciBtZWRpYW4gdmFsdWUgb2YgY29udDQgdGhhbiBjbGFpbXMgd2l0aCBgdGFyZ2V0ID09IDBgLiBIZW5jZSwgd2Ugd291bGQgZXhwZWN0IGBjb250M2AgYW5kIGBjb250NGAgdG8gaGF2ZSBuZWdhdGl2ZSByZWxhdGlvbnNoaXBzIHdpdGggdGFyZ2V0LiBJbiBhZGRpdGlvbiwgd2Ugc2VlIGEgZ3JvdXAgb2Ygb2JzZXJ2YXRpb25zIGF0IHRoZSB0YWlsIGVuZHMgZm9yIGBjb250MCwgY29udDUsIGNvbnQ3LCBjb250OCwgY29udDksIGNvbnQxMGAsIHdoaWNoIG1heSBiZSBpbmRpY2F0aXZlIG9mIG91dGxpZXJzLiAKCmBgYHtyIGNvbnQtYnktdGFyZ2V0fQojIGZsYXR0ZW4gZGYgaW50byB1c2luZyBwaXZvdF9sb25nZXIKIyBncm91cCBieSB0YXJnZXQgYW5kIHBsb3QgZGlzdHJpYnV0aW9uCmRmWywgYyhjb250X2ZlYXRzLCB0YXJnZXRfY29sKV0gJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gc3RhcnRzX3dpdGgoImNvbnQiKSwgbmFtZXNfdG8gID0gInZhciIsIHZhbHVlc190bz0idmFsdWUiKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXRhcmdldCx5PXZhbHVlKSwgZmlsbD1mYWN0b3IodmFsdWUpKSArIAogIGdlb21fYm94cGxvdCgpICsgY29vcmRfZmxpcCgpICsgZmFjZXRfd3JhcCh+dmFyLCBzY2FsZXM9ImZyZWVfeCIpCmBgYAoKRnJvbSBvdXIgY29uZGl0aW9uYWwgYm94cGxvdHMsIHdlIGNhbiBpZGVudGlmeSBwb3RlbnRpYWwgb3V0bGllcnMgYnkgZmlsdGVyaW5nIG9ic2VydmF0aW9ucyB0aGF0IGxpZSBhdCB0aGUgZXh0cmVtZSBwZXJjZW50aWxlcyBvZiB0aG9zZSBjb250aW51b3VzIHZhcmlhYmxlcy4gIEluIHBhcnRpY3VsYXIgd2Ugbm90ZSB0aGF0IHRoZXJlIGFyZSBtYW55IHZhbHVlcyBhdCB0aGUgZXh0cmVtZSBxdWFudGlsZXMgZm9yIHRoZSB2YXJpYWJsZXMgYGNvbnQwLCBjb250NSwgY29udDcsIGNvbnQ4LCBjb250OSwgY29udDEwYC4gV2UgaW52ZXN0aWdhdGUgdGhpcyBmdXJ0aGVyIHVzaW5nIHRoZSAqSGFtcGVsIGZpbHRlciosIHdoaWNoIGNvbnNpZGVycyBwb2ludHMgbHlpbmcgb3V0c2lkZSB0aGUgbWVkaWFuIHBsdXMgb3IgbWludXMgMyBtZWFuIGFic29sdXRlIGRldmlhdGlvbnMgYXMgb3V0bGllcnMuIFRocm91Z2ggdGhpcyBhcHByb2FjaCwgbW9yZSB0aGFuIDIwMCBvYnNlcnZhdGlvbnMgcGVyIHZhcmlhYmxlIHdvdWxkIGJlIGlkZW50aWZpZWQgYXMgb3V0bGllcnMsIHdoaWNoIHN1Z2dlc3RzIHRoYXQgdGhlc2UgbWF5IG5vdCBiZSBvdXRsaWVycyBidXQgdGhhdCB0aGUgZGlzdHJpYnV0aW9uIGlzIGp1c3QgaGVhdnktdGFpbGVkLiBXaXRob3V0IGZ1cnRoZXIgaW5mb3JtYXRpb24gb24gdGhlIHJlYXNvbmFibGUgc2NhbGUgb2YgdmFsdWVzIHRoYXQgaW5kaXZpZHVhbCB2YXJpYWJsZXMgY2FuIHRha2UgKGFsb25nIHdpdGggdGhlIGZhY3QgdGhhdCB0aGV5IGFyZSBhbGwgbm9ybWFsaXNlZCksIHdlIGZpbmQgaXQgY2hhbGxlbmdpbmcgdG8gaWRlbnRpZnkgb3V0bGllcnMgdGhyb3VnaCBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzIGFuZCBkZWNpZGUgbm90IHRvIGV4Y2x1ZGUgYW55IHN1Y2ggcG9pbnRzIHVzaW5nIHRoaXMgYXBwcm9hY2guIFdlIHdpbGwgcHJvY2VlZCB0byBkZXRlY3Qgb3V0bGllcnMgaW4gYW5vdGhlciBhcHByb2FjaCBpbiBbc3Vic2VxdWVudCBtb2RlbGxpbmddKCNvdXRsaWVyLWRldGVjdGlvbikuCgoKYGBge3J9CiMgTUFEIGZpbHRlcgpoYW1wZWxfZmlsdGVyIDwtIGZ1bmN0aW9uKGRmKXsKICAgbG93ZXJfYm91bmQgPC0gbWVkaWFuKGRmKSAtIDMgKiBtYWQoZGYsIGNvbnN0YW50ID0gMSkKICAgdXBwZXJfYm91bmQgPC0gbWVkaWFuKGRmKSArIDMgKiBtYWQoZGYsIGNvbnN0YW50ID0gMSkKICAgb3V0bGllcl9pbmQgPC0gd2hpY2goZGYgPCBsb3dlcl9ib3VuZCB8IGRmID4gdXBwZXJfYm91bmQpCiAgIHJldHVybihvdXRsaWVyX2luZCkKfQojIHF1YW50aWxlIGZpbHRlcgpwZXJjZW50aWxlX2ZpbHRlciA8LSBmdW5jdGlvbihkZiwgbHEgPSAwLjAwMSwgdXEgPSAwLjk5OSl7CiAgIGxvd2VyX2JvdW5kIDwtIHF1YW50aWxlKGRmLCBscSkKICAgdXBwZXJfYm91bmQgPC0gcXVhbnRpbGUoZGYsIHVxKQogICBvdXRsaWVyX2luZCA8LSB3aGljaChkZiA8IGxvd2VyX2JvdW5kIHwgZGYgPiB1cHBlcl9ib3VuZCkKICAgcmV0dXJuKG91dGxpZXJfaW5kKQp9CiMgb3V0bGllciBjb3VudApoYW1wZWxfY291bnQgPC0gZnVuY3Rpb24oeCl7bGVuZ3RoKGhhbXBlbF9maWx0ZXIoeCkpfQpwY3RfY291bnQgPC0gZnVuY3Rpb24oeCl7bGVuZ3RoKHBlcmNlbnRpbGVfZmlsdGVyKHgpKX0Kb3V0bGllcl9jb3VudHMgPC0gZGZfdHJhaW5bLCBjb250X2ZlYXRzXSAlPiUgbWFwX2RmcihoYW1wZWxfY291bnQpCm91dGxpZXJfY291bnRzWzIsIF0gPC0gZGZfdHJhaW5bLCBjb250X2ZlYXRzXSAlPiUgbWFwX2RmcihwY3RfY291bnQpCm91dGxpZXJfY291bnRzCmBgYApGb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCB3ZSB1c2Ugc3RhY2tlZCBiYXIgcGxvdHMgdG8gc2hvdyB0aGUgcGVyY2VudGFnZXMgb2Ygb2JzZXJ2YXRpb25zIGluIGVhY2ggY2F0ZWdvcnkgdGhhdCBjb3JyZXNwb25kIHRvIGB0YXJnZXQgPT0gMGAgYW5kIGB0YXJnZXQgPT0gMWAgcmVzcGVjdGl2ZWx5LiBBIG11Y2ggbGFyZ2VyIHByb3BvcnRpb24gb2YgY2xhaW1zIHdpdGggYGNhdDEzID09IEJgIGFwcGVhciB0byBiZSBhc3NvY2lhdGVkIHdpdGggYHRhcmdldCA9PSAxYCBjb21wYXJlZCB0byBgY2F0MTMgPT0gQWAuIE9uIHRoZSBvdGhlciBoYW5kLCBhIG11Y2ggbGFyZ2VyIHBlcmNlbnRhZ2Ugb2YgY2xhaW1zIGNvcnJlc3BvbmQgdG8gYHRhcmdldCA9PSAwYCBpZiBgY2F0MThgIGlzIGBBYCBvciBgQmAsIHRoYW4gaWYgYGNhdDE4YCBpcyBgQ2Agb3IgYERgLgoKYGBge3IgY2F0LWJ5LXRhcmdldCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTE1fQojIGZsYXR0ZW4gZGYgaW50byB1c2luZyBwaXZvdF9sb25nZXIKIyBncm91cCBieSB0YXJnZXQgYW5kIHBsb3QgZGlzdHJpYnV0aW9uCmRmWywgYyhjYXRfZmVhdHMsIHRhcmdldF9jb2wpXSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBzdGFydHNfd2l0aCgiY2F0IiksIG5hbWVzX3RvICA9ICJjYXQiLCB2YWx1ZXNfdG89InZhbHVlIikgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbHVlLCBmaWxsPXRhcmdldCkpICsgCiAgICBnZW9tX2Jhcihwb3NpdGlvbj0iZmlsbCIpICsgCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJXaXRoaW4gZ3JvdXAgUGVyY2VudGFnZSIsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogICAgZmFjZXRfd3JhcCh+Y2F0LCBzY2FsZXM9ImZyZWVfeCIsIG5jb2wgPSA0KSArCiAgICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDQwKQpgYGAKCldlIGFsc28gaW5zcGVjdCB0aGUgY29ycmVsYXRpb24gbWF0cml4IGZvciBvdXIgKmNvbnRpbnVvdXMqIHZhcmlhYmxlcy4gVGhlcmUgc2VlbXMgdG8gYmUgYSBjbHVzdGVyIG9mIHZhcmlhYmxlcyBgY29udDEsIGNvbnQyLCBjb250OGAgdGhhdCBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBlYWNoIG90aGVyLiBUaGlzIG1heSBiZSBwb3RlbnRpYWxseSBpbmRpY2F0aXZlIG9mIG11bHRpY29sbGluZWFyaXR5LCBhIGNvbmNlcm4gd2hlbiB1c2luZyBsaW5lYXIgbW9kZWxzLiBJbiBhZGRpdGlvbiwgd2UgYWxzbyBjb25zaWRlciB0aGUgKFBlYXJzb24pIGNvcnJlbGF0aW9uIGFuZCBvdXIgY29udGludW91cyB2YXJpYWJsZXMsIGFsdGhvdWdoIGl0IGlzIG5vdCBuZWNlc3NhcmlseSBtZWFuaW5nZnVsIGluIHRoaXMgY2FzZSBnaXZlbiBvdXIgdGFyZ2V0IGlzIGJpbmFyeS4gCgpXZSBub3RlIHRoYXQgdGhlIGNvbnRpbnVvdXMgdmFyaWFibGVzIGhhdmUgUGVhcnNvbiBjb3JyZWxhdGlvbnMgb2Ygcm91Z2hseSAkWy0wLjIsIDAuMl0kLCBzdWdnZXN0aW5nIHRoYXQgdGhlcmUgaXMgc29tZSBzbWFsbCBzaWduYWwgb3IgKmluZm9ybWF0aW9uKiBiZXR3ZWVuIHRoZSBmZWF0dXJlcyBhbmQgdGhlIHRhcmdldC4gSG93ZXZlciwgdGhlIFBlYXJzb24gY29ycmVsYXRpb24gb25seSBkZXNjcmliZXMgYSAqbGluZWFyKiBhbmQgKnBhaXJ3aXNlKiBhc3NvY2lhdGlvbiBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMuIEFzIHN1Y2ggdGhlcmUgY291bGQgYWxzbyBiZSBjb21wbGV4IG5vbi1saW5lYXIgYXNzb2NpYXRpb25zIGFuZCBpbnRlcmFjdGlvbnMgYmV0d2VlbiB0aGUgdmFyaWFibGVzLCB3aGljaCB0aGUgcHJlc2VuY2Ugb2YgbXVsdGktbW9kYWwgZGlzdHJpYnV0aW9ucyBvZiB0aGUgY29udGludW91cyBmZWF0dXJlcyBpbiBvdXIgdW5pdmFyaWF0ZSBFREEgbWF5IGFsc28gYWZmaXJtLgoKYGBge3IgY29yLW1hdH0KI2hlYXRtYXAsIGhpZ2ggdmFsdWVzIGluIHJlZCwgbG93IGluIHllbGxvdwpjb3JfbWF0cml4IDwtIGNvcihkZlssIGNvbnRfZmVhdHNdKQpoZWF0bWFwKGNvcl9tYXRyaXgsIG1haW49IkNvcnJlbGF0aW9uIE1hdHJpeCAoQ2x1c3RlcmVkKSIpCmNvcl93aXRoX3RhcmdldCA8LSByb3duYW1lc190b19jb2x1bW4oZGF0YS5mcmFtZShjb3IoZGZbLCBjKGNvbnRfZmVhdHMpXSwgYXMubnVtZXJpYyhkZiR0YXJnZXQpKSkpCm5hbWVzKGNvcl93aXRoX3RhcmdldCkgPC0gYygiZmVhdHVyZSIsICJwZWFyc29uX2NvcnIiKQpjb3Jfd2l0aF90YXJnZXQgJT4lCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIoZmVhdHVyZSwgLXBlYXJzb25fY29yciksIHkgPSBwZWFyc29uX2NvcnIpKSArIAogIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKyBjb29yZF9mbGlwKCkgKyBnZ3RpdGxlKCJQZWFyc29uIENvcnJlbGF0aW9uIG9mIENvbnRpbnVvdXMgd2l0aCBUYXJnZXQiKQpgYGAKCgpXZSBjYW4gdXNlIFByaW5jaXBhbCBDb21wb25lbnRzIEFuYWx5c2lzIChQQ0EpIGFzIGEgbWVhbnMgdG8gdmlzdWFsaXNlIHRoZSBkYXRhIGluIGxvdy1kaW1lbnNpb24sIGFuZCBleGFtaW5lIHdoZXRoZXIgdGhlcmUgYXJlIGFueSBleHBsaWNpdGx5IGRpc2Nlcm5pYmxlIHRyZW5kcy4gV2UgYXBwbHkgUENBIHRvIGFsbCB0aGUgY29udGludW91cyBmZWF0dXJlcywgd2l0aG91dCBmdXJ0aGVyIHByZS1zY2FsaW5nIGlzIHJlcXVpcmVkIGdpdmVuIHRoZSBjb250aW51b3VzIGZlYXR1cmVzIGhhdmUgdmFsdWVzIGZyb20gJFswLCAxXSQuICBCeSBleWUsIHRoZXJlIGRvIG5vdCBhcHBlYXIgdG8gYmUgYW55IHNpZ25pZmljYW50IGRpZmZlcmVuY2UgaW4gdGhlIFBDQSByZXByZXNlbnRhdGlvbnMgZm9yIGVhY2ggY2xhc3MsIGFsdGhvdWdoIHRoZXJlIGFyZSBtYW55IG9ic2VydmF0aW9ucyB3aXRoIGB0YXJnZXQgPT0gMWAgY2xvc2VyIHRvIHRoZSBib3R0b20gb2YgdGhlIGVsbGlwc29pZCBmb3JtZWQgYnkgdGhlIGZpcnN0IHR3byBQQ0EgbG9hZGluZ3MuIEF0IGxlYXN0IGluIHRoZSBzcGFjZSBmb3JtZWQgYnkgdGhlIGZpcnN0IHR3byBwcmluY2lhbCBjb21wb25lbnRzLCB0aGUgY2xhc3NlcyBkbyBub3QgYXBwZWFyIHRvIGJlIGxpbmVhcmx5IHNlcGFyYWJsZSwgIC0gd2hpY2ggc3VnZ2VzdCBhIG5vbi1saW5lYXIgbWV0aG9kIG1heSBiZSBtb3JlIGVmZmVjdGl2ZSBvbiB0aGlzIGNsYXNzaWZpY2F0aW9uIHRhc2suIAoKT24gZXhhbWluYXRpb24gb2YgdGhlICpjdW11bGF0aXZlIGV4cGxhaW5lZCB2YXJpYW5jZSByYXRpbyosIHRoZSB2YXJpYXRpb24gaW4gJFxtYXRoYmZ7WH0kIGNhcHR1cmVkIGJ5ICRrJCBwcmluY2lwYWwgY29tcG9uZW50cywgd2UgZmluZCB0aGF0IHRoZSAgZmlyc3QgdHdvIHByaW5jaXBhbCBjb21wb25lbnRzIG9ubHkgY2FwdHVyZSBhYm91dCAkXGFwcHJveCA2MFwlJCB2YXJpYXRpb24gaW4gdGhlICpjb250aW51b3VzIHZhcmlhYmxlcyouIFRvIGNhcHR1cmUgJFxhcHByb3ggOTBcJSQgb2YgdGhlIHZhcmlhdGlvbiwgd2Ugd291bGQgbmVlZCA2IG9mIHRoZSBjb250aW51b3VzIGZlYXR1cmVzLCBhbmQgZm9yICRcYXBwcm94IDk1XCUgd2Ugd291bGQgbmVlZCA5LiBUaGlzIGNvdWxkIHN1Z2dlc3QgdGhhdCBpbmNsdWRpbmcgbW9yZSBmZWF0dXJlcyBjb3VsZCBiZSBiZW5lZmljaWFsLCB3aGljaCB3b3VsZCBiZSBhIGNvbmNlcm4gaWYgd2UgYXJlIHRvIGRvICAqZmVhdHVyZSBzZWxlY3Rpb24qLgoKYGBge3IgcGNhLXZpen0KIyBwY2EgbG9hZGluZ3MKcGNzIDwtIHByY29tcChkZlssY29udF9mZWF0c10pCnNldC5zZWVkKDIwMjEpCmRhdGEuZnJhbWUocGMxPXBjcyR4WywxXSwgcGMyPXBjcyR4WywyXSwgdGFyZ2V0PWRmWywgInRhcmdldCJdKSAlPiUKZ2dwbG90KGFlcyh4ID0gcGMxLCB5ID0gcGMyLCBjb2xvdXIgPSB0YXJnZXQpKSArIAogIGdlb21faml0dGVyKGFscGhhPTAuNykgKyBnZ3RpdGxlKCdQcmluY2lwYWwgQ29tcG9uZW50cycpCiMgY3VtdWxhdGl2ZSBleHBsYWluZWQgdmFyaWFuY2UgcmF0aW8KY3VtdWxfdmFyIDwtIGN1bXN1bShwY3Mkc2Rldl4yIC8gc3VtKHBjcyRzZGV2XjIpKQpnZ3Bsb3QoZGF0YS5mcmFtZShmZWF0dXJlID0gMToxMSwgY3VtdWxfdmFyID0gY3VtdWxfdmFyKSkgKyAKICBnZW9tX2xpbmUoYWVzKHggPSBmZWF0dXJlLHkgPSBjdW11bF92YXIpKSArIGdndGl0bGUoIkN1bXVsYXRpdmUgRXhwbGFpbmVkIFZhcmlhbmNlIFJhdGlvIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9cGVyY2VudCkgKyB4bGFiKCJOdW1iZXIgb2YgUHJpbmNpcGFsIENvbXBvbmVudHMiKSArIAogIHlsYWIoIkN1bXVsYXRpdmUgZXhwbGFpbmVkIFZhcmlhbmNlIFJhdGlvIikKYGBgCgojIyBNb2RlbGxpbmcKCldlIGZpcnN0IGNvbnNpZGVyIGEgbmFpdmUgbW9kZWwgdGhhdCBhbHdheXMgcHJlZGljdHMgYSBzaW5nbGUgbGFiZWwgKGluIHRoaXMgY2FzZSB0aGUgZ3JlYXRlciBhY2N1cmFjeSBpcyBvYnRhaW5lZCBieSBhbHdheXMgcHJlZGljdGluZyAkMCQsIGdpdmVuIGl0IGlzIHRoZSBtb3N0IGZyZXF1ZW50bHkgb2NjdXJyaW5nIGNsYXNzKS4gVGhlICoqYWNjdXJhY3kqKiBpbiB0aGlzIGNhc2UsIHRoZSBwcm9wb3J0aW9uIG9mIGNvcnJlY3QgbGFiZWxzICRcc3VtX3tpID0gMX1ee259IEkoeV97aX0gPSBcaGF0e3lfe2l9fSkkLCB3b3VsZCBiZSAgJDEgLSBcaGF0e3l9ID0gMC43NDUkLiBUaGlzIGlsbHVzdHJhdGVzIGEgcG90ZW50aWFsIGlzc3VlIHdpdGggdGhlIGFjY3VyYWN5IG1ldHJpYyAtIGEgImhpZ2giIGFjY3VyYWN5IG1heSBiZSByZWZsZWN0aXZlIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHRhcmdldCBhbmQgbm90IHRoZSBtb2RlbCBwZXJmb3JtYW5jZSBpdHNlbGYsIHNvIHdoZW4gY29tcGFyaW5nIG1vZGVsICphY2N1cmFjaWVzKiwgYW4gYXBwcm9wcmlhdGUgYmFzZWxpbmUgaXMgbmVlZGVkLgoKV2UgYWxzbyBjb25zaWRlciB0aGUgKipST0MtQVVDIG1ldHJpYyoqIChSZWNlaXZpbmcgT3BlcmF0b3IgQ2hhcmFjdGVyaXN0aWMgQXJlYSBVbmRlciB0aGUgQ3VydmUpLiBHaXZlbiB0aGUgcHJvYmxlbSBpcyBvbmUgcmVsYXRlZCB0byBpbnN1cmFuY2UsIHRoZSBtb2RlbGxpbmcgb3V0Y29tZSBvZiBpbnRlcmVzdCBpcyBub3Qgb25seSB0byBvYnRhaW4gdGhlIGNvcnJlY3QgcHJlZGljdGlvbnMgKGFjY3VyYWN5KSwgYnV0IGFsc28gYWNjdXJhdGUgcHJvYmFiaWxpdGllcywgd2hpY2ggdGhlIEFVQyBtZXRyaWMgY2FwdHVyZXMgdG8gc29tZSBleHRlbnQuIAoKVGhlIEFVQyBtZXRyaWMgY2FsY3VsYXRlcyB0aGUgYXJlYSB1bmRlciB0aGUgUk9DLWN1cnZlLCB0aGUgcGxvdCBvZiB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIG9yICpzZW5zaXRpdml0eSogJFxmcmFje1RQfXtUUCArIEZOfSQgYWdhaW5zdCB0aGUgZmFsc2UgcG9zaXRpdmUgcmF0ZSAkXGZyYWN7Rk59e1ROICsgRlB9JCwgYXNzZXNzaW5nIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgY2xhc3NpZmllcidzIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGFjcm9zcyBhbGwgcG9zc2libGUgZGVjaXNpb24gdGhyZXNob2xkcy4gQSBjbGFzc2lmaWVyIHRoYXQgYWx3YXlzIHByZWRpY3RzIDBzIHdvdWxkIHJlc3VsdCBpbiBhIGJhc2VsaW5lIEFVQyBvZiAkMC41JC4KCkFzIHdlIGRvIG5vdCBrbm93IHRoZSBzcGVjaWZpYyB0aHJlc2hvbGQgcmVsZXZhbnQgdG8gdGhlIGluc3VyYW5jZSBjb250ZXh0IG9mIHRoZSBwcm9ibGVtLCB3ZSB3aWxsIHJlcG9ydCBib3RoIGFjY3VyYWN5LCB3aXRoIGEgZGVjaXNpb24gdGhyZXNob2xkIHNldCB0byAkPiAwLjUkLCBhbmQgdGhlIEFVQyBzY29yZSBmb3IgYWxsIG1vZGVscyBjb25zaWRlcmVkLiBJbiBwcmFjdGljZSwgdGhlIGRlY2lzaW9uIHRocmVzaG9sZCB3b3VsZCBsYXJnZWx5IGRlcGVuZCBvbiB0aGUgcmVxdWlyZW1lbnRzIG9mIHRoZSBtb2RlbGxlci4KCgpgYGB7cn0KIyB0cmFpbi10ZXN0ClhfdHJhaW4gPSBhcy5tYXRyaXgoZGZfdHJhaW5bLCBjb250X2ZlYXRzXSkKeV90cmFpbiA9IGFzLm51bWVyaWMoYXMubWF0cml4KGRmX3RyYWluJHRhcmdldCkpClhfdGVzdCA9IGFzLm1hdHJpeChkZl90ZXN0WywgY29udF9mZWF0c10pCnlfdGVzdCA9IGFzLm51bWVyaWMoYXMubWF0cml4KGRmX3Rlc3QkdGFyZ2V0KSkKIyBoZWxwZXIgZnVuY3Rpb24gZm9yIG1ldHJpY3MKZXZhbHVhdGVfbWV0cmljcyA8LSBmdW5jdGlvbih0cmFpbl9wcmVkLCB0ZXN0X3ByZWQsIHRyYWluX3RydWUsIHRlc3RfdHJ1ZSl7CiAgdHJhaW5fY2xhc3NlcyA8LSBpZmVsc2UodHJhaW5fcHJlZCA+IDAuNSwgMSwwKQogIHRlc3RfY2xhc3NlcyA8LSBpZmVsc2UodGVzdF9wcmVkID4gMC41LCAxLDApCiAgYWNjMSA8LSBtZWFuKHRyYWluX2NsYXNzZXMgPT0gdHJhaW5fdHJ1ZSkKICBhdWMxIDwtIGF1Yyhyb2ModHJhaW5fdHJ1ZSwgdHJhaW5fcHJlZCwgcXVpZXQ9VFJVRSkpCiAgYWNjMiA8LSBtZWFuKHRlc3RfY2xhc3NlcyA9PSB0ZXN0X3RydWUpCiAgYXVjMiA8LSBhdWMocm9jKHRlc3RfdHJ1ZSwgdGVzdF9wcmVkLCBxdWlldD1UUlVFKSkKICBkYXRhLmZyYW1lKHRyYWluX2FjYz1hY2MxLCB0cmFpbl9hdWM9YXVjMSwgdGVzdF9hY2MgPSBhY2MyLCB0ZXN0X2F1Yz1hdWMyKQp9CiMgbWV0cmljcyBmb3IgbmFpdmUgcHJlZGljdGlvbgpyZXN1bHRzID0gZGF0YS5mcmFtZShldmFsdWF0ZV9tZXRyaWNzKHJlcCgwLCBkaW0oZGZfdHJhaW4pWzFdKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoMCwgZGltKGRmX3Rlc3QpWzFdKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl90cmFpbiR0YXJnZXQsIGRmX3Rlc3QkdGFyZ2V0KSwKICAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzPWMoIm5haXZlIikpCnJlc3VsdHMKYGBgCgojIyMgTG9naXN0aWMgUmVncmVzc2lvbiAoU0dEKQoKVG8gZnVsZmlsbCB0aGUgcHJvamVjdCByZXF1aXJlbWVudHMsIHdlIGltcGxlbWVudCBhIGBmcm9tIHNjcmF0Y2gnIFN0b2NoYXN0aWMgR3JhZGllbnQgRGVzY2VudCByb3V0aW5lICBmb3IgTG9naXN0aWMgUmVncmVzc2lvbi4gVGhlIGRlcml2YXRpb24gZm9sbG93cyBbQGhhc3RpZTIwMDllbGVtZW50cywgcHAuIDEyMC0xMjZdCgpDb25zaWRlciBhIG1hdHJpeCBvZiB2YXJpYWJsZXMgJFggPSAoeF97MX0sIHhfezJ9LCBcbGRvdHMgeF97bn0pXntUfSQsIHdoZXJlICR4X3tpfSQgZGVub3RlIHRoZSBpLXRoIHJvdyBvciBvYnNlcnZhdGlvbiwgdGhlIHRhcmdldCAkXG1hdGhiZnt5fSA9ICh5X3sxfSwgeV97Mn0sIFxsZG90cywgeV97bn0pJC4gTGV0ICRcZnJhY3twKHhfe2l9OyBcYmV0YSl9ezEgLSBwKHhfe2l9IDsgXGJldGEpfSA9IGV4cChcYmV0YV57VH0geF97aX0pJCAgZGVub3RlIHRoZSBvZGRzIHJhdGlvLgoKV2UgYXJlIGludGVyZXN0ZWQgaW4gZmluZGluZyB0aGUgY29lZmZpY2llbnRzICRcYmV0YSQsIHN1Y2ggdGhhdCB0aGUgKipsb2dpc3RpYyBsb3NzKiogKGFsc28gcmVmZXJyZWQgdG8gYXMgYmluYXJ5IGNyb3NzLWVudHJvcHksIGFuZCBlcXVpdmFsZW50IHRvIHRoZSBuZWdhdGl2ZSBsb2ctbGlrZWxpaG9vZCkgaXMgbWluaW1pc2VkLiBUaGUgbG9naXN0aWMgbG9zcyBpcyBnaXZlbiBieToKCiQkXGJlZ2lue2FsaWdufWwoXGJvbGRzeW1ib2x7XGJldGF9KSAmPSAtXHN1bV97aSA9IDF9XntOfSB5X3tpfSBsb2cocCh4X3tpfSA7IFxib2xkc3ltYm9se1xiZXRhfSkpICsgKDEgLSB5X3tpfSkgbG9nKDEgLSBwKHhfe2l9IDsgXGJvbGRzeW1ib2x7XGJldGF9KSlcXCAKJj0gXHN1bV97aSA9IDF9XntOfSBcbGVmdCBbIHlfe2l9bG9nIFxsZWZ0IChcZnJhY3twKHhfe2l9IDsgXGJvbGRzeW1ib2x7XGJldGF9KX17MSAtIHAoeF97aX0gOyBcYm9sZHN5bWJvbHtcYmV0YX0pfSBccmlnaHQpICsgbG9nKDEgLSBwKHhfe2l9IDsgXGJvbGRzeW1ib2x7XGJldGF9KSkgXHJpZ2h0IF1cXAogbChcYm9sZHN5bWJvbHtcYmV0YX0pICY9IC1cc3VtX3tpID0gMX1ee059XGxlZnQgW3lfe2l9IFxib2xkc3ltYm9se1xiZXRhfV57VH0geF97aX0gLSBsb2coMSArIGV4cChcYm9sZHN5bWJvbHtcYmV0YX1ee1R9eF97aX0pKSBccmlnaHQgXSBcdGFnezF9ClxlbmR7YWxpZ259JCQgCgpXaXRoIHRoZSBpbmNsdXNpb24gb2YgYSByZWd1bGFyaXNhdGlvbiB0ZXJtLCBzcGVjaWZpY2FsbHkgYSAkTF57Mn0kIHBlbmFsdHksIHRoZSBsb3NzIGZ1bmN0aW9uIGJlY29tZXM6CgokJCBcYmVnaW57ZXF1YXRpb259bChcYm9sZHN5bWJvbHtcYmV0YX0pID0gLVxzdW1fe2kgPSAxfV57Tn0gXGxlZnQgW3lfe2l9IFxib2xkc3ltYm9se1xiZXRhfV57VH0geF97aX0gLSBsb2coMSArIGV4cChcYm9sZHN5bWJvbHtcYmV0YX1ee1R9eF97aX0pKSBccmlnaHQgXSAtIFxsYW1iZGEgXGJvbGRzeW1ib2x7XGJldGF9XntUfSBcYm9sZHN5bWJvbHtcYmV0YX0gXHRhZ3syfSBcZW5ke2VxdWF0aW9ufSQkIAoKVGhlIGdyYWRpZW50IG9mIHRoZSBsb3NzIGZ1bmN0aW9uIHdpdGggcmVzcGVjdCB0byB0aGUgY29lZmZpY2llbnRzICRcbWF0aGJme1xiZXRhfSQgaXMgZ2l2ZW4gYnk6CgokJFxuYWJsYShcYm9sZHN5bWJvbHtcYmV0YX0pID0gLVxzdW1fe2kgPSAxfV57Tn0gXGxlZnQgWyB5X3tpfSB4X3tpfSAtIFxmcmFje3hfe2l9ZXhwKFxib2xkc3ltYm9se1xiZXRhfV57VH0geF97aX0pfXsxICsgZXhwKFxib2xkc3ltYm9se1xiZXRhfV57VH0geF97aX0pfSBccmlnaHRdIC0gXGxhbWJkYSAyXGJvbGRzeW1ib2x7XGJldGF9JCQKCldlIGFwcGx5IChzdG9jaGFzdGljKSBncmFkaWVudCBkZXNjZW50LiBJbiBzaG9ydCwgdGhpcyByZWxpZXMgb24gdXBkYXRpbmcgdGhlIGNvZWZmaWNpZW50cyAkXGJldGEkIGl0ZXJhdGl2ZWx5IGJhc2VkIG9uIGEgc3RlcCBzaXplICRcbGFtYmRhJDoKCiQkXGJldGFfe3QgKyAxfSA9IFxiZXRhX3t0fSAtIFxsYW1iZGEgXG5hYmxhKFxib2xkc3ltYm9se1xiZXRhfV97dH0pJCQKCk1vcmUgc29waGlzdGljYXRlZCBhcHByb2FjaGVzIHN1Y2ggYXMgTW9tZW50dW0gb3IgQWRhbSBjYW4gYmUgdXNlZCBmb3IgdGhlIGl0ZXJhdGl2ZSB1cGRhdGVzOyB0aGVzZSBhcmUgYmV0dGVyIG91dGxpbmVkIGluIFtAdmlzaG5vaTIwMjBvcHRpbWlzYXRpb25dLiBJbiBvdXIgc2ltcGxlIGltcGxlbWVudGF0aW9uLCB3ZSB1c2UgdGhlIEJhcnppbGFpLUJvcndlaW4gbWV0aG9kIFtAbXVycGh5MjAxMm1hY2hpbmUsIHBwLiA0NDQtNDQ1XSB0byBkZXRlcm1pbmUgdGhlIHN0ZXAgc2l6ZS4KCiQkXGxhbWJkYV97dH0gPSBcZnJhY3t8KFxiZXRhX3t0fSAtIFxiZXRhX3t0IC0gMX0pXntUfShcbmFibGEgRihcYmV0YV97dH0pIC0gIFxuYWJsYSBGKFxiZXRhX3t0IC0gMX0pKXx9e1x8IFxuYWJsYSBGKFxiZXRhX3t0fSkgLSAgXG5hYmxhIEYoXGJldGFfe3QgLSAxfSkpXHxeezJ9fSQkCkdpdmVuIHN1ZmZpY2llbnQgaXRlcmF0aW9ucyBhbmQgYXBwcm9wcmlhdGUgc3RlcCBzaXplLCB0aGUgbW9kZWwgc2hvdWxkIGNvbnZlcmdlIChpLmUuIHRoZSBjaGFuZ2UgaW4gdGhlIGNvZWZmaWNpZW50cyAkXHxcYmV0YV97dCArIDF9IC0gXGJldGFfe3R9XHwkIG9yIHRoZSBjaGFuZ2UgaW4gbG9zcyBpcyBsZXNzIHRoYW4gc29tZSB0aHJlc2hvbGQgJFxlcHNpbG9uJCksIGFsdGhvdWdoIHRoaXMgaXMgbm90IGd1YXJhbnRlZWQuICAgIAoKYGBge3IgZ3JhZGllbnQtZGVzY2VudH0KIyBiaW5hcnkgY3Jvc3NlbnRyb3B5IC8gbG9nLWxvc3MKbG9nX2xvc3MgPC0gZnVuY3Rpb24oeCwgeSwgYmV0YXMsIGxhbWJkYSl7CiAgbG9naXRzIDwtIHggJSolIGJldGFzCiAgLSAodCh5KSAlKiUgbG9naXRzIC0gc3VtKGxvZygxICsgZXhwKGxvZ2l0cykpKSArIGxhbWJkYSAqIHQoYmV0YXMpICUqJSBiZXRhcykgLyBkaW0oeClbMV0KfQojIGxvZ2lzdGljIHJlZ3Jlc3Npb24gZ3JhZGllbnRzCmdyYWRpZW50cyA8LSBmdW5jdGlvbih4LCB5LCBiZXRhcywgbGFtYmRhKXsKICBsb2dpdHMgPC0geCAlKiUgYmV0YXMKICAtICh0KHgpICUqJSAoeSAtIGV4cChsb2dpdHMpLygxICsgZXhwKGxvZ2l0cykpKSkgLSBsYW1iZGEgKjIgKiBiZXRhcyAvIGRpbSh4KVsxXQp9CiMgcHJlIHNldCBwYXJhbWV0ZXJzCnAgPSBkaW0oWF90cmFpbilbMl0KbGFtYmRhID0gMApuX2l0ZXJzIDwtIDEwMAppbml0X3N0ZXBfc2l6ZSA8LSAxZS02CnNldC5zZWVkKDIwMjEpCmJldGFfaW5pdCA8LSBtYXRyaXgocm5vcm0ocCksbnJvdz1wKQpiZXRhX3BhdGggPC0gbWF0cml4KHJlcCgwLCBuX2l0ZXJzICogcCksIG5yb3cgPSBuX2l0ZXJzLCBuY29sPXApCmJldGFfcGF0aFsxLF0gPSBiZXRhX2luaXQKbGFzdF9ncmFkIDwtIGdyYWQgPC0gZ3JhZGllbnRzKFhfdHJhaW4sIHlfdHJhaW4sIGJldGFfcGF0aFsxLF0sIGxhbWJkYSkKYmV0YV9wYXRoWzIsXSA9IGJldGFfaW5pdCAtIGluaXRfc3RlcF9zaXplICogZ3JhZApncmFkIDwtIGdyYWRpZW50cyhYX3RyYWluLCB5X3RyYWluLCBiZXRhX3BhdGhbMixdLCBsYW1iZGEpCmxvc3NlcyA8LSByZXAoMCwgbl9pdGVycykKIyBpdGVyYXRpdmVseSB1cGRhdGUgYmV0YXMKZm9yIChpIGluIDM6bl9pdGVycyl7CiAgICBzdGVwX3NpemUgPC0gYXMubnVtZXJpYyh0KGJldGFfcGF0aFtpIC0gMSxdIC0gYmV0YV9wYXRoW2kgLSAyLF0pICUqJSAoZ3JhZCAtIGxhc3RfZ3JhZCkgLyAKICAgICAgICAgICAgICAgICAgICAodChncmFkIC0gbGFzdF9ncmFkKSAlKiUgKGdyYWQgLSBsYXN0X2dyYWQpKSkKICAgIGJldGFfcGF0aFtpLF0gPC0gYmV0YV9wYXRoW2kgLSAxLF0gLSBzdGVwX3NpemUgKiBncmFkCiAgICBsYXN0X2dyYWQgPC0gZ3JhZAogICAgZ3JhZCA8LSBncmFkaWVudHMoWF90cmFpbiwgeV90cmFpbiwgYmV0YV9wYXRoW2ksIF0sIGxhbWJkYSkKICAgIGxvc3Nlc1tpXSA8LSBsb2dfbG9zcyhYX3RyYWluLCB5X3RyYWluLCBiZXRhX3BhdGhbaSxdLCBsYW1iZGEpCn0KIyBwbG90ICsgcmVzdWx0cwpnZ3Bsb3QoZGF0YS5mcmFtZShzdGVwID0gMzpuX2l0ZXJzLCBsb3NzPWxvc3Nlc1szOm5faXRlcnNdKSkgKyAKICBnZW9tX2xpbmUoYWVzKHggPSBzdGVwLCB5ID0gbG9zcykpICsKICBnZ3RpdGxlKCJCaW5hcnkgQ3Jvc3NlbnRyb3B5IHZzLiBJdGVyYXRpb25zIikKcHJlZF90cmFpbiA8LSBhcy5udW1lcmljKDEgLyAoMSArIGV4cCgtWF90cmFpbiAlKiUgYmV0YV9wYXRoWzEwMCxdKSkpCnByZWRfdGVzdCA8LSBhcy5udW1lcmljKDEgLyAoMSArIGV4cCgtWF90ZXN0ICUqJSBiZXRhX3BhdGhbMTAwLF0pKSkKcmVzdWx0c1sic2dkIixdIDwtIGV2YWx1YXRlX21ldHJpY3MocHJlZF90cmFpbiwgcHJlZF90ZXN0LCBkZl90cmFpbiR0YXJnZXQsIGRmX3Rlc3QkdGFyZ2V0KQpyZXN1bHRzWyJzZ2QiLF0KYGBgCgpXZSBhcHBseSBvdXIgImZyb20gc2NyYXRjaCIgaW1wbGVtZW50YXRpb24gb2YgTG9naXN0aWMgUmVncmVzc2lvbiB1c2luZyBTR0Qgd2l0aCBhbGwgdGhlIG51bWVyaWNhbCB2YXJpYWJsZXMsIG9idGFpbmluZyBhIHRlc3QgYWNjdXJhY3kgb2YgJDAuNzUxJCBhbmQgYSB0ZXN0IEFVQyBvZiAkMC43MDgyJCwgYSBzbWFsbCBpbXByb3ZlbWVudCBpbiB0ZXJtcyBvZiBhY2N1cmFjeSBmcm9tIHRoZSBuYWl2ZSBzdHJhdGVneSwgYnV0IGEgbGFyZ2VyIGltcHJvdmVtZW50IGluIHRlcm1zIG9mIEFVQy4KCioqUmVtYXJrKio6IFdlIG5vdGUgdGhhdCBvdXIgaW1wbGVtZW50YXRpb24gb2YgTG9naXN0aWMgUmVncmVzc2lvbiBpcyBxdWl0ZSBsaW1pdGVkLCBmb3IgZXhhbXBsZSBhbiBpbmFiaWxpdHkgdG8gcHJvY2VzcyB0aGUgKmZhY3RvciogZGF0YXR5cGUgKHVubGVzcyB3ZSBhcHBlbmQgYSBvbmUtaG90IGVuY29kZWQgbWF0cml4KSwgb3IgYW4gaW5hYmlsaXR5IHRvIGV4YW1pbmUgY29lZmZpY2llbnQgc3RhbmRhcmQgZXJyb3JzICh1bmxlc3Mgd2UgY2hhbmdlIHRvIGEgSGVzc2lhbiBiYXNlZCBhcHByb2FjaCAvIE5ld3RvbidzIG1ldGhvZCkuIEdpdmVuIHRoZSBsaW1pdGF0aW9ucyBvZiBvdXIgaW1wbGVtZW50YXRpb24sIHdlIHNoYWxsIHN1YnNlcXVlbnRseSB1c2UgdGhlIGBnbG1gIHBhY2thZ2UgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24sIGFuZCB0aGUgYGdsbW5ldGAgcGFja2FnZSBmb3IgcmVndWxhcmlzZWQgbG9naXN0aWMgcmVncmVzc2lvbi4KCgojIyMgTG9naXN0aWMgUmVncmVzc2lvbiAoRmV3IFByZWRpY3RvcnMpCgpIYXZpbmcgZXhwbG9yZWQgdGhlIGVmZmljYWN5IG9mIGxvZ2lzdGljIHJlZ3Jlc3Npb24gb24gdGhpcyBtb2RlbGxpbmcgdGFzaywgd2Ugbm93IHV0aWxpc2UgYSBtb3JlIHJvYnVzdCBpbXBsZW1lbnRhdGlvbiBvZiBsb2dpc3RpYyByZWdyZXNzaW9uIHZpYSB0aGUgYGdsbWAgcGFja2FnZS4gV2UgY29uc2lkZXIgYSBtb3JlIHBhcnNpbW9uaW91cyBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLCBoYW5kLXBpY2tpbmcgc2V2ZXJhbCBmZWF0dXJlcyBiYXNlZCBvbiBvdXIgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcy4gV2Ugc2VsZWN0IHRoZSBmZWF0dXJlcyB0aGF0IGhhdmUgZGlzY2VybmlibGUgZGlmZmVyZW5jZXMgaW4gdGhlaXIgZGlzdHJpYnV0aW9ucyBjb25kaXRpb25lZCBvbiB0aGUgdGFyZ2V0OiAgdGhlIHByZWRpY3RvcnMgYGNvbnQzLCBjb250NCwgY2F0MTMsIGNhdDE4YC4KCkJ5IHNlbGVjdGluZyBmZXcgcHJlZGljdG9ycywgY291cGxlZCB3aXRoIHRoZSBpbi1idWlsdCBmZWF0dXJlcyBvZiB0aGUgYGdsbWAgaW1wbGVtZW50YXRpb24sIHdlIGNhbiBvYnRhaW4sIHRvIHNvbWUgZXh0ZW50LCAqaW50ZXJwcmV0YWJpbGl0eSogYW5kIHVuZGVyc3RhbmRpbmcgb2YgdGhlIG1vZGVsLiBUaGlzIHdpbGwgaGVscCBndWlkZSBvdXIgYW5hbHlzaXMsIGFuZCBpZGVudGlmeSBhbnkgcG90ZW50aWFsIGlzc3VlcyBiZWZvcmUgZXhwYW5kaW5nIHRvIG1vcmUgcHJlZGljdG9ycyBvciBkaWZmZXJlbnQgbW9kZWxzLiAgV2Ugd2lsbCBhbHNvIHVzZSB0aGlzIGFzIGEgKipiYXNlbGluZSoqIHRvIGNvbXBhcmUgdGhlIHBlcmZvcm1hbmNlIG9mIHN1YnNlcXVlbnQgbW9kZWxzIHdpdGguIAoKYGBge3IgZ2xtLWJhc2VsaW5lfQpnbG1fYmFzZSA8LSBnbG0odGFyZ2V0fmNvbnQzK2NvbnQ0K2NhdDEzK2NhdDE4LCBkYXRhID0gZGZfdHJhaW4sIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpKQpzdW1tYXJ5KGdsbV9iYXNlKQp2aWYoZ2xtX2Jhc2UpCmdnY29lZihnbG1fYmFzZSkKYGBgCgoKRmlyc3RseSwgdGhlIHByZWRpY3RvcnMgYXJlIGFsbW9zdCBhbGwgKnN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQqIGF0IGEgNSUgbGV2ZWwsIHdpdGggdGhlIGV4Y2VwdGlvbiBvZiBgY2F0MThCYCBhbmQgdGhlIGludGVyY2VwdC4gSW5zcGVjdGluZyB0aGUgY29lZmZpY2llbnQgcGxvdHMsIHdoaWNoIHZpc3VhbGlzZSB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2YgZWFjaCBjb2VmZmljaWVudCBlc3RpbWF0ZSwgd2Ugb2JzZXJ2ZSB0aGF0IHRoZSBjb25maWRlbmNlIGludGVydmFscyBvZiBhbGwgdmFyaWFibGVzIGV4Y2VwdCBgY2F0MThCYCBhcmUgc2lnbmlmaWNhbnQgYXQgNSUgbGV2ZWwsIGFuZCBkbyBub3QgaW5jbHVkZSAwLiBXZSBhbHNvIHNlZSB0aGF0IHRoZSB3aWR0aCBvZiB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2YgdGhlIGNvZWZmaWNpZW50cyBvZiBgY2F0MThEYCBhbmQgYGNhdDE4Q2AsIHdoaWNoIGFyZSBwcm9wb3J0aW9uYWwgdG8gdGhlaXIgdmFyaWFuY2VzLCBhcmUgbXVjaCBsYXJnZXIgdGhhbiB0aGF0IG9mIGBjYXQxM0JgLCBgY29udDRgIGFuZCBgY29udDNgLCBob3dldmVyIGFzIHRoZSBjYXRlZ29yaWNhbCBhbmQgY29udGludW91cyB2YXJpYWJsZXMgYXJlIG9uIGNvbXBsZXRlbHkgZGlmZmVyZW50IHNjYWxlcywgdGhlc2UgYXJlIG5vdCBkaXJlY3RseSBjb21wYXJhYmxlLiBJdCBjb3VsZCBiZSB0aGF0IHRoaXMgaXMgYSByZXN1bHQgb2Ygc3Vic2FtcGxpbmcsIGFuZCB0aGF0IGFzIHdlIGluY3JlYXNlIHRoZSBzYW1wbGUgc2l6ZSB0aGUgc3RhbmRhcmQgZXJyb3JzIG9mIHRoZSBjb2VmZmljaWVudHMgd291bGQgZGVjcmVhc2UuCgpTZWNvbmRseSwgd2UgaW5zcGVjdCB0aGUgKmdlbmVyYWxpc2VkIHZhcmlhbmNlIGluZmxhdGlvbiBmYWN0b3IgKEdWSUYpKi4gVGhlICp2YXJpYW5jZSBpbmZsYXRpb24gZmFjdG9yKiBjYXB0dXJlcyB0aGUgZXh0ZW50IHRvIHdoaWNoIHRoZSBzdGFuZGFyZCBlcnJvciBvZiBhIHByZWRpY3RvciBpcyBpbmNyZWFzZWQgZHVlIHRvIGl0cyBjb3JyZWxhdGlvbiB3aXRoIG90aGVyIHByZWRpY3RvcnMgaW4gdGhlIG1vZGVsLCBjb3JyZWN0ZWQgYnkgdGhlIG51bWJlciBvZiBkZWdyZWVzIG9mIGZyZWVkb20gY29ycmVzcG9uZGluZyB0byB0aGUgbnVtYmVyIG9mIGxldmVscyBpbiB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBUaGUgR1ZJRiBvZiBhbGwgcHJlZGljdG9ycyBpbiBvdXIgbW9kZWwgYXJlIGxlc3MgdGhhbiAyLCB3aGljaCBzdWdnZXN0cyB0aGVyZSBpcyBubyBldmlkZW5jZSBvZiBtdWx0aWNvbGxpbmVhcml0eS4gIEFsdGhvdWdoIG91ciBhbmFseXNpcyBvZiB0aGUgKnZhcmlhbmNlIGluZmxhdGlvbiBmYWN0b3IqIHN1Z2dlc3RzIHRoYXQgdGhlcmUgaXMgbm8gbXVsdGljb2xsaW5lYXJpdHksIGEgcXVpY2sgaW5zcGVjdGlvbiBvZiB0aGUgdmFyaWFibGUgYGNhdDE4YCBpbmRpY2F0ZXMgdGhhdCB0aGVyZSBhcmUgdmVyeSBmZXcgb2JzZXJ2YXRpb25zIGluIHRoZSBsZXZlbCBgY2F0MThBYDsgdGh1cyBgY2F0MThCYCB3b3VsZCBiZSBhcHByb3hpbWF0ZWx5IGNsb3NlIHRvIGAxIC0gY2F0MThDIC0gY2F0MThEYCBhbmQgcG90ZW50aWFsbHkgY29sbGluZWFyIHdoZW4gdGhlIGludGVyY2VwdCBpcyBjb25zaWRlcmVkLnMgVGhpcyBzdWdnZXN0cyB0aGF0IGEgbW9yZSByb2J1c3QgbWV0aG9kIHRvIGVuY29kZSB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGNvdWxkIGJlIHJlcXVpcmVkLCBzdWNoIGFzIG1lcmdpbmcgaW5mcmVxdWVudCBjYXRlZ29yaWVzLCAgb3IgcGVyaGFwcyB1c2luZyByZWd1bGFyaXNhdGlvbiBpbnN0ZWFkLgoKYGBge3J9CnRhYmxlKGRmX3RyYWluJGNhdDE4KQpgYGAKICAKQXNzdW1pbmcgdGhhdCB0aGVyZSBpcyBubyBtdWx0aWNvbGxpbmVhcml0eSwgdGhlIGNvZWZmaWNpZW50cyBmb3IgZWFjaCBwcmVkaWN0b3IgcmVwcmVzZW50cyB0aGUgYXNzb2NpYXRpb24gb2YgdGhhdCBwcmVkaWN0b3Igd2l0aCB0aGUgbG9nLW9kZHMgb2YgaGF2aW5nIGB0YXJnZXQgPT0gMWA6ICAkXGxvZyBcbGVmdCAoIFxmcmFje1AoXHRleHR7dGFyZ2V0fT0xKX17UFx0ZXh0eyh0YXJnZXR9PTApfSBccmlnaHQgKSQuCgpUaGUgY29lZmZpY2llbnRzIG9mIHRoZSBkdW1teSB2YXJpYWJsZXMgaW5kaWNhdGUgdGhlIGF2ZXJhZ2UgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBsb2cgb2RkcyBvZiB0aGF0IGZhY3RvciBsZXZlbCBncm91cCBjb21wYXJlZCB0byB0aGUgYmFzZWxpbmUgbGV2ZWwgZ3JvdXAuIEluIG91ciByZWdyZXNzaW9uLCB0aGUgZmlyc3QgY2F0ZWdvcnkgKEEpIG9mIGVhY2ggY2F0ZWdvcmljYWwgdmFyaWFibGUgaXMgdGFrZW4gYXMgdGhlIGJhc2VsaW5lIGdyb3VwLiBGb3IgZXhhbXBsZSwgdGhlIGNvZWZmaWNpZW50IG9mIGBjYXQxM0JgIGltcGxpZXMgdGhlIGZvbGxvd2luZyBlcXVhdGlvbjoKJCRcbG9nXGZyYWN7UCh0YXJnZXQ9MXxjYXQxMz1CKX17UCh0YXJnZXQ9MHxjYXQxMz1CKX0tXGxvZ1xmcmFje1AodGFyZ2V0PTF8Y2F0MTM9QSl9e1AodGFyZ2V0PTB8Y2F0MTM9QSl9PTEuODY4MSQkCgoKVGhpcyBzdWdnZXN0cyB0aGF0IGNsYWltcyB3aXRoIGBjYXQxMz1CYCBoYXZlICRleHAoMS44NjgxKS0xPTUuNDgkIGhpZ2hlciBvZGRzIHRoYW4gY2xhaW1zIHdpdGggYGNhdDEzPUFgLiBHaXZlbiB0aGF0IHRoZSBjb2VmZmljaWVudCBvZiBgY2F0MThCYCBpcyBub3Qgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCAob3IgaW4gb3RoZXIgd29yZHMsIGl0cyBzdGFuZGFyZCBlcnJvciBpcyBoaWdoKSwgdGhpcyBzdWdnZXN0cyB0aGF0IGl0cyBhc3NvY2lhdGlvbiB3aXRoIHRhcmdldCBtYXkgbm90IGJlIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGZyb20gYGNhdDE4PUFgIChpbnNwZWN0aW5nIHRoZSBjb25kaXRpb25hbCBwbG90IGZyb20gb3VyIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMsIHRoZWlyIGNvbmRpdGlvbmFsIGRpc3RyaWJ1dGlvbnMgb24gdGhlIHRhcmdldCBhcHBlYXIgdG8gYmUgc2ltaWxhcikuIEhvd2V2ZXIsIGhhdmluZyBgY2F0MTg9Q2AgYXMgb3Bwb3NlZCB0byBoYXZpbmcgYGNhdDE4PUFgICBpcyBhc3NvY2lhdGVkIHdpdGggYSAkZXhwKDIuNjA4MSktMT0xMi42JCBsYXJnZXIgaW5jcmVhc2UgaW4gdGhlIGxvZy1vZGRzIG9mIGhhdmluZyBgdGFyZ2V0PT0xYC4gU2ltaWxhcmx5LCBjbGFpbXMgd2l0aCBgY2F0MTg9RGAgYXJlIGFzc29jaWF0ZWQgd2l0aCBhICRleHAoMi40MzQyKS0xPTkuNCQgZ3JlYXRlciBsb2ctbGlrZWxpaG9vZCBvZiBoYXZpbmcgYHRhcmdldD09MWAuCgpUbyBpbnRlcnByZXQgdGhlIGNvZWZmaWNpZW50cyBvZiB0aGUgY29udGludW91cyB2YXJpYWJsZXMsIGxvZyBvZGRzIG5lZWQgdG8gYmUgY2FsY3VsYXRlZCB1c2luZyBzcGVjaWZpYyBwYWlycyBvZiB2YWx1ZXMgb2YgdGhlIHByZWRpY3RvcnMuIFRoZSBub24tbGluZWFyaXR5IG9mIHRoZSBsb2dpc3RpYyBmdW5jdGlvbiBtZWFucyB0aGF0IGEgY2hhbmdlIG9mIG9uZSB1bml0IGluIHRoZSB2YWx1ZSBvZiBhIHByZWRpY3RvciBpcyBub3QgdGhlIHNhbWUgYWNyb3NzIHRoZSByYW5nZSBvZiB0aGUgcHJlZGljdG9yLiBgY29udDNgIGFuZCBgY29udDRgIGhhdmUgbmVnYXRpdmUgY29lZmZpY2llbnRzIGFzIGV4cGVjdGVkLCBhbmQgYXJlIGludGVycHJldGVkIGFzIGZvbGxvd3MuIEEgb25lIHVuaXQgaW5jcmVhc2UgaW4gYGNvbnQzYCBpcyBhc3NvY2lhdGVkIHdpdGggYW4gJGV4cCgtMC42NTU0KT0wLjUxOSQgbXVsdGlwbGljYXRpdmUgZWZmZWN0IG9uIHRoZSBvZGRzLCBpLmUuIGEgNDglIGRlY3JlYXNlIGluIG9kZHMgY29tcGFyZWQgdG8gdGhlIHByZXZpb3VzIG9kZHMuIFNpbWlsYXJseSwgYSBjbGFpbSB3aXRoIGEgb25lIHVuaXQgaGlnaGVyIHZhbHVlIG9mIOKAmGNvbnQ04oCZIGlzICQxIC0gZXhwKC0wLjQ3NzIpPTM3LjlcJSQgbGVzcyBsaWtlbHkgdG8gaGF2ZSBgdGFyZ2V0PT0xYCB0aGFuIGEgY2xhaW0gd2l0aCBhIG9uZSB1bml0IGxvd2VyIHZhbHVlIG9mIGBjb250NGAuIEFzIHRoZSB0cnVlIHJhbmdlIG9mIGVhY2ggY29udGludW91cyB2YXJpYWJsZSBiZWZvcmUgbm9ybWFsaXNhdGlvbiBpcyB1bmtub3duLCBpdCBpcyB1bmNsZWFyIHdoZXRoZXIgdGhpcyBhc3NvY2lhdGVkIGNoYW5nZSBpbiB0YXJnZXQgaXMgY29uc2lkZXJlZCBsYXJnZSBpbiByZWFsaXR5LgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHJlZF90cmFpbiA8LSBwcmVkaWN0KGdsbV9iYXNlLCBkZl90cmFpbiwgdHlwZT0icmVzcG9uc2UiKQpwcmVkX3Rlc3QgPC0gcHJlZGljdChnbG1fYmFzZSwgZGZfdGVzdCwgdHlwZT0icmVzcG9uc2UiKQpyZXN1bHRzWyJnbG0tYmFzZSIsXSA8LSBldmFsdWF0ZV9tZXRyaWNzKHByZWRfdHJhaW4sIHByZWRfdGVzdCwgZGZfdHJhaW4kdGFyZ2V0LCBkZl90ZXN0JHRhcmdldCkKcmVzdWx0c1siZ2xtLWJhc2UiLF0KYGBgCgpXaXRoIGp1c3QgYSBmZXcgcHJlZGljdG9ycywgdGhlIGJhc2VsaW5lIG1vZGVsIGhhcyBhIHRlc3QgYWNjdXJhY3kgb2YgJDgwLjNcJSQsIHdoaWNoIGlzICQ3LjVcJSQgaGlnaGVyIHRoYW4gdGhlIHRlc3QgYWNjdXJhY3kgb2YgdGhlIG5haXZlIG1vZGVsLiBUaGUgdGVzdCBBVUMgb2YgdGhlIGJhc2VsaW5lIG1vZGVsIGlzICQwLjcwMyQsIHdoaWNoIGlzIDAuMjAzIGhpZ2hlciB0aGFuIHRoYXQgb2YgdGhlIG5haXZlIG1vZGVsLCB3aGljaCBpcyBhIGZhaXIgaW1wcm92ZW1lbnQuCgojIyMgTG9naXN0aWMgUmVncmVzc2lvbiAoQWxsIFByZWRpY3RvcnMpCgpXZSBub3cgcnVuIGEgbG9naXN0aWMgcmVncmVzc2lvbiB1c2luZyBhbGwgMzEgcHJlZGljdG9ycy4gVGhlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGNhbiBiZSBpbnRlcnByZXRlZCBpbiBhIHNpbWlsYXIgd2F5IGFzIHRoZSBiYXNlbGluZSBtb2RlbC4gVGhlIHJlZ3Jlc3Npb24gb3V0cHV0IHNob3dzIHRoYXQgYWxsIHByZWRpY3RvcnMgYXJlIHNpZ25pZmljYW50IGF0IDUlLCBidXQgdGhpcyBpcyB1bnJlbGlhYmxlIHNpbmNlIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaGF2ZSBtYW55IGxldmVscyAoNjIzIGluIHRvdGFsKS4gCgpgYGB7ciBnbG0tZnVsbC1yZXN1bHRzLCBtZXNzYWdlPUZBTFNFfQpnbG1fZnVsbCA8LSBnbG0odGFyZ2V0fi4sIGRhdGE9ZGZfdHJhaW4sIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpLCAKICAgICAgICAgICAgY29udHJvbCA9IGxpc3QobWF4aXQgPSAxMDApKQoKcHJlZF90cmFpbiA8LSBwcmVkaWN0KGdsbV9mdWxsLCBkZl90cmFpbiwgdHlwZT0icmVzcG9uc2UiKQpnbG1fZnVsbCR4bGV2ZWxzID0gbGFwcGx5KGRmWyxjYXRfZmVhdHNdLCBsZXZlbHMpCnByZWRfdGVzdCA8LSBwcmVkaWN0KGdsbV9mdWxsLCBkZl90ZXN0LCB0eXBlPSJyZXNwb25zZSIpCnJlc3VsdHNbImdsbS1mdWxsIixdIDwtIGV2YWx1YXRlX21ldHJpY3MocHJlZF90cmFpbiwgcHJlZF90ZXN0LCBkZl90cmFpbiR0YXJnZXQsIGRmX3Rlc3QkdGFyZ2V0KQpyZXN1bHRzWyJnbG0tZnVsbCIsXQpgYGAKClRoZSBmdWxsIG1vZGVsIGhhcyBhIGhpZ2hlciB0cmFpbiBhY2N1cmFjeSAoMC44MDEpIGJ1dCBsb3dlciB0ZXN0IGFjY3VyYWN5ICgwLjU5KSB0aGFuIHRoZSBiYXNlbGluZSBtb2RlbCwgd2hpY2ggaW1wbGllcyBvdmVyZml0dGluZy4gVGhlIHRlc3QgQVVDIGlzIDAuNTQzLCB3aGljaCBpcyBtdWNoIGxvd2VyIHRoYW4gdGhlIGJhc2VsaW5lLiBUaGlzIGlzIGFuIGV4YW1wbGUgb2YgdGhlIGJpYXMtdmFyaWFuY2UgdHJhZGUtb2ZmOyB0aGUgbW9yZSBjb21wbGV4LCBmdWxsIG1vZGVsIHdpdGggMzEgcHJlZGljdG9ycyBoYXMgYSBsb3dlciBiaWFzIGJ1dCBoaWdoZXIgdmFyaWFuY2UgdGhhbiB0aGUgc2ltcGxlciwgYmFzZWxpbmUgbW9kZWwgd2l0aCA0IHByZWRpY3RvcnMuIAoKV2UgYWxzbyBvYnRhaW4gYW4gZXJyb3I6IOKAnHByZWRpY3Rpb24gZnJvbSBhIHJhbmstZGVmaWNpZW50IGZpdCBtYXkgYmUgbWlzbGVhZGluZ+KAnSB3aGljaCBzdWdnZXN0cyBtdWx0aWNvbGxpbmVhcml0eS4gSW4gc3Vic2VxdWVudCBzZWN0aW9ucywgd2UgYWltIHRvIG92ZXJjb21lIHRoaXMgdG8gaW1wcm92ZSB0aGUgc3RhYmlsaXR5IG9mIG91ciBtb2RlbCB1c2luZyBzaHJpbmthZ2UgaW4gYSBbc3Vic2VxdWVudCBzZWN0aW9uXSgjcmVndWxhcmlzZWQtbG9naXN0aWMtcmVncmVzc2lvbikuCgojIyMgT3V0bGllciBEZXRlY3Rpb24KQXQgdGhpcyBzdGFnZSwgd2Ugc3VzcGVjdCB0aGF0IHNvbWUgb3V0bGllcnMgbWlnaHQgYmUgaGVhdmlseSBpbmZsdWVuY2luZyB0aGUgcGVyZm9ybWFuY2Ugb2Ygb3VyIG1vZGVscywgYW5kIGhlbmNlIHdlIHNlZWsgdG8gZGV0ZWN0IG91dGxpZXJzIHVzaW5nIGEgZmV3IG1lYXN1cmVzLiBGb3JtYWxseSwgb3V0bGllcnMgYXJlIGRlZmluZWQgYXMgb2JzZXJ2YXRpb25zIHdpdGggYSByZXNwb25zZSB2ZWN0b3IgdGhhdCBpcyB1bnVzdWFsIGNvbmRpdGlvbmFsIG9uIGNvdmFyaWF0ZXMgKHByZWRpY3RvcnMpLiBGaXJzdGx5LCB3ZSBsb29rIGZvciBwb2ludHMgd2l0aCBsYXJnZSAoc3R1ZGVudGlzZWQpIHJlc2lkdWFscy4gV2UgY2FuIHRoZW4gdGVzdCBpZiB0aGVzZSByZXNpZHVhbHMgYXJlIHNpZ25pZmljYW50bHkgbGFyZ2VyIHRoYW4gdGhvc2Ugb2Ygb3RoZXIgcG9pbnRzIGJ5IGV4YW1pbmluZyB0aGUgQm9uZmVycm9uaS1hZGp1c3RlZCBwLXZhbHVlcy4gVGVuIHBvaW50cyBhcmUgaWRlbnRpZmllZCB0byBoYXZlIGxhcmdlIHN0dWRlbnRpc2VkIHJlc2lkdWFscyB3aXRoIGFkanVzdCBwLXZhbHVlcyBsZXNzIHRoYW4gMC4wNS4gV2Ugc3RvcmUgdGhlIGluZGljZXMgb2YgdGhlc2UgcG9pbnRzIHRvIGJlIHJlbW92ZWQgbGF0ZXIuCgpgYGB7cn0Kb3V0bGllclRlc3QoZ2xtX2Z1bGwpCm91dGxpZXJzIDwtIGFzLm51bWVyaWMobmFtZXMob3V0bGllclRlc3QoZ2xtX2Z1bGwpJHApKQpgYGAKCk9ic2VydmF0aW9ucyB0aGF0IGFyZSBmYXIgZnJvbSB0aGUgYXZlcmFnZSBjb3ZhcmlhdGUgcGF0dGVybiBhcmUgY29uc2lkZXJlZCB0byBoYXZlIGhpZ2ggbGV2ZXJhZ2UgYW5kIGNhbiBiZSBtZWFzdXJlZCB1c2luZyBoYXQgdmFsdWVzLiBIZXJlLCB0aGVyZSBhcmUgYXQgbGVhc3QgMTAwIHBvaW50cyB3aXRoIGhpZ2ggbGV2ZXJhZ2UuIEZpbmFsbHksIHdlICBhbHNvIG1lYXN1cmUgZm9yIGluZmx1ZW5jZSwgd2hpY2ggaXMgYW4gb2JzZXJ2YXRpb24gdGhhdCBpcyBhbiBvdXRsaWVyIGFuZCBoYXZlIGhpZ2ggbGV2ZXJhZ2UuIFRoZXNlIGFyZSBsaWtlbHkgdG8gaW5mbHVlbmNlIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyBhbmQgaW5mbHVlbmNlIGNhbiBiZSB0aG91Z2h0IG9mIGFzIHRoZSBwcm9kdWN0IG9mIGxldmVyYWdlIGFuZCBvdXRsaWVyLiBIZXJlLCB3ZSBwbG90IHN0dWRlbnRpc2VkIHJlc2lkdWFscyBhZ2FpbnN0IGhhdC12YWx1ZXMgd2l0aCB0aGUgc2l6ZSBvZiBhIGNpcmNsZSBiZWluZyBwcm9wb3J0aW9uYWwgdG8gdGhlIENvb2sncyBkaXN0YW5jZSBvZiBhbiBvYnNlcnZhdGlvbi0gYSBtZWFzdXJlIG9mIGluZmx1ZW5jZS4KCgpgYGB7ciBpbmZsdWVuY2V9CmluZmx1ZW5jZUluZGV4UGxvdChnbG1fZnVsbCwgdmFycyA9ICJoYXQiKQppbmZsdWVuY2VQbG90KGdsbV9mdWxsKQpgYGAKCldlIG9ic2VydmUgdGhhdCB0aGVyZSBhcmUgNCBvYnNlcnZhdGlvbnMgd2l0aCBoaWdoIGluZmx1ZW5jZS4gV2UgcmVtb3ZlIHRoZXNlIG9ic2VydmF0aW9ucyBhbG9uZyB3aXRoIHRoZSBwb2ludHMgaWRlbnRpZmllZCBhcyBvdXRsaWVycyBlYXJsaWVyICgxNCBpbiB0b3RhbCksIGFuZCBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBvdXIgdXBkYXRlZCBtb2RlbCB3aXRoIHRoZSBvcmlnaW5hbCBtb2RlbCAoc2VlIFtBcHBlbmRpeF0oI2FwcGVuZGl4KSkuIAoKV2UgZmluZCB0aGF0IHJlbW92aW5nIG91dGxpZXJzIChzZWUgW0FwcGVuZGl4XSgjYXBwZW5kaXgpKSBsZWFkcyB0byBhIGhpZ2hlciB0ZXN0IGFjY3VyYWN5IG9mIDAuNjg0IGJ1dCBhIGxvd2VyIHRlc3QgQVVDIG9mIDAuNTM5LiBXZSBjYW5ub3QgaW50ZXJwcmV0IHRoaXMgZnVydGhlciB3aXRob3V0IGtub3dpbmcgdGhlIHNwZWNpZmljIHRocmVzaG9sZCB1c2VkIHRvIGNsYXNzaWZ5IHRoZSB0YXJnZXQgaW4gdGhlIGRhdGEuIEFuYWx5c2luZyB0aGUgcmVncmVzc2lvbiBvdXRwdXQsIHdlIHNlZSB0aGF0IGxlYXZpbmcgb3V0IHRoZSBvdXRsaWVycyBkb2VzIG5vdCBjaGFuZ2UgdGhlIGNvZWZmaWNpZW50IGVzdGltYXRlcywgYW5kIHNpbmNlIHdlIGhhdmUgbm8gaW50dWl0aXZlIHJlYXNvbiB0byBiZWxpZXZlIHRoYXQgdGhlc2Ugb3V0bGllciB2YWx1ZXMgYXJlIGV4dHJlbWUgKGR1ZSB0byBubyBrbm93bGVkZ2UgYWJvdXQgdGhlIHZhcmlhYmxlcyB0aGVtc2VsdmVzKSwgd2UgZGVjaWRlZCB0byBrZWVwIHRoZXNlIG91dGxpZXIgZGF0YSBwb2ludHMgZm9yIGFsbCBvdGhlciBtb2RlbHMuIEZ1cnRoZXJtb3JlLCBpdCBpcyBwb3NzaWJsZSB0aGF0IHRoZXNlIG91dGxpZXJzIG1heSBiZSBsZXNzIGltcGFjdGZ1bCBhcyB3ZSBpbmNyZWFzZSB0aGUgc2FtcGxlIHNpemUuCgoKIyMjIFJlZ3VsYXJpc2VkIExvZ2lzdGljIFJlZ3Jlc3Npb24KCkdpdmVuIHRoZSBpc3N1ZSBvZiAqaGlnaCBkaW1lbnNpb25hbGl0eSosIHdlIGNvbnNpZGVyIGEgcmVndWxhcmlzZWQgZm9ybSBvZiBsb2dpc3RpYyByZWdyZXNzaW9uLiBTcGVjaWZpY2FsbHksIHdlIGNvbnNpZGVyIHJpZGdlIChsb2dpc3RpYykgcmVncmVzc2lvbiwgd2l0aCB0aGUgZnVuY3Rpb25hbCBmb3JtIGFzIGluIFtlcXVhdGlvbiAoMildKCNsb2dpc3RpYy1yZWdyZXNzaW9uLVwoc2dkXCkpIFRoaXMgbWV0aG9kIHNocmlua3MgY29lZmZpY2llbnRzIGJ5IGltcG9zaW5nIGEgcGVuYWx0eSBvbiB0aGVpciBzaXplLCB0aGVyZWJ5IHJlZHVjaW5nIG1vZGVsIGNvbXBsZXhpdHkuICBXZSB1c2UgdGhlIGltcGxlbWVudGF0aW9uIG9mZmVyZWQgYnkgdGhlICBgZ2xtbmV0YCBwYWNrYWdlOyBpbiBkb2luZyBzbyB3ZSBuZWVkIHRvIGNvbnZlcnQgdGhlIGRhdGEgdHlwZSBpbnRvIG1hdHJpY2VzLlRoZSBgZ2xtbmV0YCBwYWNrYWdlIHJlcXVpcmVzIHRoZSBkYXRhIHRvIGJlIGluIGEgKm1hdHJpeCogZGF0YSB0eXBlLCBhbmQgaGVuY2Ugd2UgbWFrZSB0aGUgY29ycmVzcG9uZGluZyBhZGp1c3RtZW50LgoKYGBge3IgZ2xtLXJpZGdlLXNjb3JlcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KWF90cmFpbiA9IGRmX3RyYWluWywgLWxlbmd0aChkZl90cmFpbildCnlfdHJhaW4gPC0gZGZfdHJhaW4kdGFyZ2V0ClhfdGVzdCA9IGRmX3Rlc3RbLCAtbGVuZ3RoKGRmX3Rlc3QpXQp5X3Rlc3QgPC0gZGZfdGVzdCR0YXJnZXQKWF90cmFpbiA9IG1vZGVsLm1hdHJpeCh+LiwgWF90cmFpbikKWF90ZXN0ID0gbW9kZWwubWF0cml4KH4uLCBYX3Rlc3QpCmdsbV9yZWcgPC0gY3YuZ2xtbmV0KFhfdHJhaW4sIHlfdHJhaW4sIAogICAgICAgICAgICAgICAgICBmYW1pbHk9ImJpbm9taWFsIihsaW5rPSJsb2dpdCIpLCBhbHBoYT0wKQpwcmVkX3RyYWluIDwtIGFzLm51bWVyaWMocHJlZGljdChnbG1fcmVnLCBYX3RyYWluLCB0eXBlPSJyZXNwb25zZSIpKQpwcmVkX3Rlc3QgPC0gYXMubnVtZXJpYyhwcmVkaWN0KGdsbV9yZWcsIFhfdGVzdCwgdHlwZT0icmVzcG9uc2UiKSkKcmVzdWx0c1siZ2xtLXJpZGdlIixdIDwtIGV2YWx1YXRlX21ldHJpY3MocHJlZF90cmFpbiwgcHJlZF90ZXN0LCBkZl90cmFpbiR0YXJnZXQsIGRmX3Rlc3QkdGFyZ2V0KQpyZXN1bHRzWyJnbG0tcmlkZ2UiLF0KYGBgCgojIyMgUmFuZG9tIEZvcmVzdAoKVG8gaW1wcm92ZSBwZXJmb3JtYW5jZSwgd2UgZHJhdyBvbiB0aGUgdXNhZ2Ugb2Ygbm9uLWxpbmVhciB0cmVlLWJhc2VkIG1vZGVscywgc3BlY2lmaWNhbGx5IHJhbmRvbSBmb3Jlc3RzW0BicmVpbWFuMjAxNnJhbmRvbWZvcmVzdHNdLiBJbnR1aXRpdmVseSwgYSByYW5kb20gZm9yZXN0IGF2ZXJhZ2VzIGRpZmZlcmVudCBkZWNpc2lvbiB0cmVlcyAoa25vd24gYXMgYmFnZ2luZykgc28gYXMgdG8gcmVkdWNlIHRoZSB2YXJpYW5jZSBvZiBpbmRpdmlkdWFsIHRyZWVzLiBUaGlzIHRodXMgcmVkdWNlcyB0aGUgbGlrZWxpaG9vZCBvZiBpbmRpdmlkdWFsIHRyZWVzIG92ZXJmaXR0aW5nIHRvIHRoZSB0cmFpbmluZyBkYXRhLgoKYGBge3IgcmYgLG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmZfcmVjaXBlIDwtIHJlY2lwZSh0YXJnZXR+LiwgZGF0YSA9IGRmX3RyYWluKQoKcmZfbW9kZWwgPC0gCiAgcmFuZF9mb3Jlc3QodHJlZXM9MTAwKSAlPiUKICBzZXRfZW5naW5lKCJyYW5nZXIiLCBpbXBvcnRhbmNlPSJpbXB1cml0eSIsIHNlZWQ9MjAyMSkgJT4lCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikKCnJmX3dvcmtmbG93IDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX3JlY2lwZShyZl9yZWNpcGUpICU+JQogIGFkZF9tb2RlbChyZl9tb2RlbCkKCnJmMSA8LSBmaXQocmZfd29ya2Zsb3csIGRmX3RyYWluKQpyYW5nZXJfb2JqIDwtIHB1bGxfd29ya2Zsb3dfZml0KHJmMSkkZml0CiMgcGxvdCBmZWF0dXJlIGltcG9ydGFuY2VzCnJmX2ZlYXRfaW1wIDwtIHJvd25hbWVzX3RvX2NvbHVtbihkYXRhLmZyYW1lKHJhbmdlcl9vYmokdmFyaWFibGUuaW1wb3J0YW5jZSkpOwpuYW1lcyhyZl9mZWF0X2ltcCkgPC0gYygiZmVhdCIsICJpbXBvcnRhbmNlIikKZ2dwbG90KHJmX2ZlYXRfaW1wLCBhZXMoeCA9IHJlb3JkZXIoZmVhdCwgaW1wb3J0YW5jZSksIHkgPSBpbXBvcnRhbmNlKSkrIAogICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uPSJkb2RnZSIpKyBjb29yZF9mbGlwKCkrCiAgICAgIHlsYWIoIkZlYXR1cmUgSW1wb3J0YW5jZSAoR2luaSBJbXB1cml0eSkiKSsKICAgICAgeGxhYigiRmVhdHVyZSIpKwogICAgICBnZ3RpdGxlKCJSYW5kb20gRm9yZXN0IEZlYXR1cmUgSW1wb3J0YW5jZXMiKQojZXZhbHVhdGUgbWV0cmljcwpwcmVkX3RyYWluIDwtIHVubGlzdChwcmVkaWN0KHJmMSxkZl90cmFpbix0eXBlPSdwcm9iJylbLDJdKQpwcmVkX3Rlc3QgPC0gdW5saXN0KHByZWRpY3QocmYxLCBkZl90ZXN0LCB0eXBlPSdwcm9iJylbLDJdKQpyZXN1bHRzWyJyZiIsXSA8LSBldmFsdWF0ZV9tZXRyaWNzKHByZWRfdHJhaW4sIHByZWRfdGVzdCwgZGZfdHJhaW4kdGFyZ2V0LCBkZl90ZXN0JHRhcmdldCkKcmVzdWx0c1sicmYiLF0KYGBgCgoKCmBgYHtyIHBkcCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KdGVtcCA8LSBmdW5jdGlvbih4KXtwYXJ0aWFsKHJhbmdlcl9vYmosIHRyYWluPWRmX3RyYWluLCBwcmVkLnZhciA9IHgsIHBsb3QgPSBUUlVFLCBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIiwgcGFyb3B0cyA9IGxpc3QoLnBhY2thZ2VzID0gInJhbmdlciIpKX0KcGxvdHMgPC0gbGFwcGx5KG5hbWVzKGRmX3RyYWluKVtjb250X2ZlYXRzXSwgdGVtcCkKd3JhcF9wbG90cyhwbG90cykKYGBgCgpUaGUgcmFuZG9tIGZvcmVzdCBoYXMgYSB0ZXN0IGFjY3VyYWN5IG9mIDAuODM2IGFuZCBhIHRlc3QgQVVDIG9mIDAuODcyLCB3aGljaCBpcyBoaWdoZXIgdGhhbiB0aGUgcHJldmlvdXMgbGluZWFyIG1vZGVscy4gSG93ZXZlciwgdGhlIHRyYWluIGFjY3VyYWN5IGFuZCBBVUMgYXJlIHNpZ25pZmljYW50bHkgaGlnaGVyIHRoYW4gdGhlIHRlc3QgcGVyZm9ybWFuY2UsIHdoaWNoIHN1Z2dlc3QgdGhhdCB0aGVyZSBtYXkgYmUgc29tZSBkZWdyZWUgb2Ygb3ZlcmZpdHRpbmcgdG8gdGhlIHRyYWluIGRhdGEuIAoKUmFuZG9tIGZvcmVzdHMgY2FuIGJlIHVzZWQgdG8gcmFuayB0aGUgaW1wb3J0YW5jZSBvZiBkaWZmZXJlbnQgZmVhdHVyZXMuIFNwZWNpZmljYWxseSwgdGhlIHgtYXhpcyBpcyB0aGUgTWVhbiBEZWNyZWFzZSBBY2N1cmFjeSwgd2hpY2ggcmVwb3J0cyBob3cgbXVjaCBhY2N1cmFjeSB0aGUgbW9kZWwgbG9zZXMgd2hlbiB3ZSBleGNsdWRlIHRoaXMgdmFyaWFibGUuIFRoZSBtb3JlIHRoZSBhY2N1cmFjeSBmYWxscyBieSwgdGhlIG1vcmUgaW1wb3J0YW50IHRoZSBwYXJ0aWN1bGFyIHZhcmlhYmxlIGlzLiBOb3RlIGhlcmUgdGhhdCBmb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCBlYWNoIGxldmVsIG9mIHRoZSBjYXRlZ29yeSBpcyBjbGFzc2lmaWVkIGFzIGEgc2luZ2xlIHZhcmlhYmxlLiBJbiB0aGlzIHBsb3QsIHdlIHJlY29yZGVkIHRoZSAzMCBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMuIAoKV2UgdGhlbiBydW4gYW5vdGhlciByYW5kb20gZm9yZXN0IHdpdGggdGhlIG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzLiBJbiBkb2luZyBzbywgd2UgaG9wZSB0byByZWR1Y2UgdGhlIGRlZ3JlZSBvZiBvdmVyZml0dGluZyBieSByZWR1Y2luZyB0aGUgY29tcGxleGl0eSBvZiB0aGUgbW9kZWwuIEhvd2V2ZXIsIGl0IGRvZXMgbm90IG1ha2Ugc2Vuc2UgdG8gZHJvcCBzb21lIGxldmVscyBvZiBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlIHdoaWxlIGluY2x1ZGluZyB0aGUgb3RoZXIgbGV2ZWxzLiBIZW5jZSwgYXMgbG9uZyBhcyBhIGxldmVsIGlzIHByZXNlbnQgaW4gdGhlIHRvcCAzMCBmZWF0dXJlcywgd2Ugd2lsbCBpbmNsdWRlIHRoZSBlbnRpcmUgY2F0ZWdvcnkgaW4gb3VyIHVwZGF0ZWQgcmFuZG9tIGZvcmVzdCBtb2RlbC4gVGhpcyByZXN1bHRzIGluIHVzIGtlZXBpbmcgb25seSAyMiB2YXJpYWJsZXMsIGZyb20gYW4gaW5pdGlhbCAzMC4gCgpPdXIgcmVkdWNlZCByYW5kb20gZm9yZXN0IGhhcyBhIHNsaWdodGx5IGltcHJvdmVkIHRlc3QgYWNjdXJhY3kgYW5kIGEgc2xpZ2h0bHkgZGVjcmVhc2VkIHRlc3QgQVVDLiBIb3dldmVyLCBpdCBkb2VzIG5vdCBzb2x2ZSB0aGUgcG90ZW50aWFsIHByb2JsZW0gb2Ygb3ZlcmZpdHRpbmcgYXMgdHJhaW4gcGVyZm9ybWFuY2UgaXMgc3RpbGwgc2lnbmlmaWNhbnRseSBiZXR0ZXIgdGhhbiB0ZXN0IHBlcmZvcm1hbmNlLiBJbiBmYWN0LCB0cmFpbiBwZXJmb3JtYW5jZSBvbiB0aGUgcmVkdWNlZCByYW5kb20gZm9yZXN0IGlzIGJldHRlciB0aGFuIHRoZSByYW5kb20gZm9yZXN0IHdpdGggYSBmdWxsIHNldCBvZiB2YXJpYWJsZXMgLSB0aGUgcmVkdWNlZCBjb21wbGV4aXR5IG9mIHRoZSBtb2RlbCBlbmFibGVkIGl0IHRvIGhhdmUgYSBsb3dlciBiaWFzIG9uIHRoZSB0cmFpbmluZyBzZXQuIAoKCiMjIyBYR0Jvb3N0CgpGb3IgY29tcGxldGVuZXNzLCB3ZSBhbHNvIGNvbnNpZGVyIHRoZSBgeGdib29zdGAgbGlicmFyeSBmb3IgKipncmFkaWVudCBib29zdGVkIGRlY2lzaW9uIHRyZWVzKiouIEdyYWRpZW50IEJvb3N0ZWQgRGVjaXNpb24gVHJlZXMuIFRoZSBYR0Jvb3N0IHBhY2thZ2UsIGludHJvZHVjZWQgaW4gW0BjaGVuMjAxNnhnYm9vc3RdIGlzIGEgdmFyaWFudCBvZiBHcmFkaWVudCBCb29zdGVkIERlY2lzaW9uIFRyZWVzIFtAaGFzdGllMjAwOWVsZW1lbnRzLCBwcC4gMzUzLTM3NF0uIEZvciBHcmFkaWVudCBCb29zdGluZyBUcmVlcywgZWFjaCBkZWNpc2lvbiB0cmVlIGlzIHRyYWluZWQgc2VxdWVudGlhbGx5IG9uIHRoZSAqcmVzaWR1YWxzKiBvZiB0aGUgcHJldmlvdXMgZGVjaXNpb24gdHJlZSwgd2hpY2ggaGFzIHRoZSBlZmZlY3Qgb2YgcmVkdWNpbmcgYmlhcy4KCmBgYHtyIHhnYiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZG15X3RyYWluIDwtIGR1bW15VmFycygifi4iLCBkYXRhID0gZGZfdHJhaW5bLC1sZW5ndGgoZGZfdHJhaW4pXSkKZG15X3Rlc3QgPC0gZHVtbXlWYXJzKCJ+LiIsIGRhdGEgPSBkZl90ZXN0WywtbGVuZ3RoKGRmX3Rlc3QpXSkKWF90cmFpbiA8LSBhcy5tYXRyaXgoZGF0YS5mcmFtZShwcmVkaWN0KGRteV90cmFpbixkZl90cmFpbikpKQpYX3Rlc3QgPC0gYXMubWF0cml4KGRhdGEuZnJhbWUocHJlZGljdChkbXlfdGVzdCxkZl90ZXN0KSkpCnlfdHJhaW4gPSBhcy5pbnRlZ2VyKGFzLm1hdHJpeChkZl90cmFpbiR0YXJnZXQpKQp5X3Rlc3QgPSBhcy5pbnRlZ2VyKGFzLm1hdHJpeChkZl90ZXN0JHRhcmdldCkpCmJzdCA8LSB4Z2Jvb3N0KGRhdGEgPSBYX3RyYWluLCBsYWJlbD15X3RyYWluLCBtYXhfZGVwdGggPSAyLCBucm91bmQgPSAxMCwgCiAgICAgICAgICAgICAgIHZlcmJvc2U9MCwKICAgICAgICAgICAgICAgb2JqZWN0aXZlPSdiaW5hcnk6bG9naXN0aWMnLAogICAgICAgICAgICAgICBldmFsX21ldHJpYz0ibG9nbG9zcyIpCgpwcmVkX3RyYWluIDwtIHByZWRpY3QoYnN0LCBYX3RyYWluLCB0eXBlPSJyZXNwb25zZSIpCnByZWRfdGVzdCA8LSBwcmVkaWN0KGJzdCwgWF90ZXN0LCB0eXBlPSJyZXNwb25zZSIpCnJlc3VsdHNbInhnYiIsXSA8LSBldmFsdWF0ZV9tZXRyaWNzKHByZWRfdHJhaW4sIHByZWRfdGVzdCwgZGZfdHJhaW4kdGFyZ2V0LCBkZl90ZXN0JHRhcmdldCkKcmVzdWx0c1sieGdiIixdCgppbXBvcnRhbmNlX21hdHJpeCA8LSB4Z2IuaW1wb3J0YW5jZShtb2RlbD1ic3QpCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jZV9tYXRyaXgpCiMgc2hhcGxleSB2YWx1ZXMgIAp4Z2Jvb3N0Ojp4Z2IuZ2dwbG90LnNoYXAuc3VtbWFyeShYX3Rlc3QsIG1vZGVsID0gYnN0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0X2NsYXNzID0gMSwgdG9wX24gPSAyMCkgICMgU3VtbWFyeSBwbG90CmBgYAoKIyMjIENhdEJvb3N0CgpXZSBhbHNvIGNvbnNpZGVyIGJyaWVmbHkgZXhhbWluZSB0aGUgYGNhdGJvb3N0YCBsaWJyYXJ5IGZvciAqKmdyYWRpZW50IGJvb3N0ZWQgZGVjaXNpb24gdHJlZXMqKiwgZ2l2ZW4gaXRzIHBvcHVsYXJpdHkgb24gbWFjaGluZSBsZWFybmluZyBjb21wZXRpdGlvbnMgc3VjaCBhcyBLYWdnbGUuIFRoZSBDYXRCb29zdCBsaWJyYXJ5IGhhcyB0aGUgYWR2YW50YWdlIG9mIGxlYXJuaW5nIGEgKnRhcmdldCBlbmNvZGluZyosIGkuZS4gc29tZSBudW1lcmljYWwgdmFsdWUgZm9yIGV2ZXJ5IGNhdGVnb3J5IGZvciBlYWNoICBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuIEZyb20gYW4gaW1wbGVtZW50YXRpb24gcG9pbnQgb2YgdmlldywgdGhpcyBtYXkgcmVkdWNlIHRoZSBwcmVwcm9jZXNzaW5nIHRoYXQgbWF5IGJlIHJlcXVpcmVkLiBGb3IgZGV0YWlscyBvbiBDYXRCb29zdCwgcmVmZXIgdG8gW0Bwcm9raG9yZW5rb3ZhMjAxN2NhdGJvb3N0XS4gCgpBZ2FpbiwgdGhlIGtleSBwYXJhbWV0ZXJzIGFyZSB0aGUgbnVtYmVyIG9mIHRyZWVzIChpdGVyYXRpb25zKSwgYW5kIHRoZSAqZGVwdGgqIG9mIGVhY2ggdHJlZS4gRHVlIHRvIGNvbXB1dGF0aW9uYWwgbGltdGF0aW9ucywgd2Ugc2VsZWN0IGFyYml0cmFyeSBwYXJhbWV0ZXJzIGBpdGVyYXRpb25zID0gMTBgIGFuZCBgZGVwdGggPSA4YDsgdGhlc2UgY291bGQgYmUgZnVydGhlciBmaW5lLXR1bmVkIHVzaW5nIGh5cGVycGFyYW1ldGVyIG9wdGltaXNhdGlvbi4KCmBgYHtyIGNiLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpYX3RyYWluID0gZGZfdHJhaW5bLGMoY2F0X2ZlYXRzLCBjb250X2ZlYXRzKV0KeV90cmFpbiA9IGFzLmludGVnZXIoZGZfdHJhaW4kdGFyZ2V0KQpYX3Rlc3QgPSBkZl90ZXN0WyxjKGNhdF9mZWF0cywgY29udF9mZWF0cyldCnlfdGVzdCA9IGFzLmludGVnZXIoZGZfdGVzdCR0YXJnZXQpCgpwb29sIDwtIGNhdGJvb3N0LmxvYWRfcG9vbChYX3RyYWluLCB5X3RyYWluLCBjYXRfZmVhdHVyZXMgPSBjYXRfZmVhdHMpCm1vZGVsIDwtIGNhdGJvb3N0LnRyYWluKHBvb2wsIHBhcmFtcz1saXN0KGRlcHRoID0gOCwgaXRlcmF0aW9ucyA9IDEwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9zc19mdW5jdGlvbj0nTG9nbG9zcycsIHZlcmJvc2U9MCkpCgpwcmVkX3RyYWluIDwtIGNhdGJvb3N0LnByZWRpY3QobW9kZWwsIGNhdGJvb3N0LmxvYWRfcG9vbChYX3RyYWluKSwgcHJlZGljdGlvbl90eXBlID0gJ1Byb2JhYmlsaXR5JykKcHJlZF90ZXN0IDwtIGNhdGJvb3N0LnByZWRpY3QobW9kZWwsIGNhdGJvb3N0LmxvYWRfcG9vbChYX3Rlc3QpLCBwcmVkaWN0aW9uX3R5cGUgPSAnUHJvYmFiaWxpdHknKQoKIyBmZWF0dXJlIGltcG9ydGFuY2UKZmVhdF9pbXBvcnRhbmNlIDwtIGNhdGJvb3N0LmdldF9mZWF0dXJlX2ltcG9ydGFuY2UobW9kZWwsIHBvb2wpCmltcG9ydGFuY2VzIDwtIGRhdGEuZnJhbWUoZmVhdF9pbXBvcnRhbmNlW29yZGVyKGZlYXRfaW1wb3J0YW5jZSwgZGVjcmVhc2luZz1GQUxTRSksXSkKaW1wb3J0YW5jZXMkZmVhdHVyZXMgPSByb3duYW1lcyhpbXBvcnRhbmNlcykKbmFtZXMoaW1wb3J0YW5jZXMpIDwtIGMoImltcG9ydGFuY2UiLCJmZWF0dXJlcyIpCmltcG9ydGFuY2VzJGZlYXR1cmVzIDwtIGZhY3RvcihpbXBvcnRhbmNlcyRmZWF0dXJlcywgbGV2ZWw9aW1wb3J0YW5jZXMkZmVhdHVyZXMpCmdncGxvdChpbXBvcnRhbmNlcywgYWVzKHg9aW1wb3J0YW5jZSwgeT1mZWF0dXJlcykpICsgCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgZ2d0aXRsZSgiQ2F0Qm9vc3QgRmVhdHVyZSBJbXBvcnRhbmNlIikKCiNTaGFwbGV5IFZhbHVlcwpkYXRhX3NoYXBfdHJlZSA8LSBjYXRib29zdC5nZXRfZmVhdHVyZV9pbXBvcnRhbmNlKG1vZGVsLCBwb29sID0gcG9vbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gIlNoYXBWYWx1ZXMiKQpkYXRhX3NoYXBfdHJlZSA8LSBkYXRhLmZyYW1lKGRhdGFfc2hhcF90cmVlWywgLW5jb2woZGF0YV9zaGFwX3RyZWUpXSkgCm5hbWVzKGRhdGFfc2hhcF90cmVlKSA9IG5hbWVzKGRmWywgYyhjYXRfZmVhdHMsIGNvbnRfZmVhdHMpXSkKCiMgZ2dwbG90KHN0YWNrKGRhdGFfc2hhcF90cmVlKSwgYWVzKHggPSBpbmQsIHkgPSB2YWx1ZXMpKSArCiMgICAgIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdmFsdWVzKSkgKyBjb29yZF9mbGlwKCkgKyAKIyAgICAgZ2d0aXRsZSgiU2hhcGxleSBWYWx1ZXMgYnkgdmFyaWFibGUiKSAgKyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKQoKcmVzdWx0c1siY2F0Ym9vc3QiLF0gPC0gZXZhbHVhdGVfbWV0cmljcyhwcmVkX3RyYWluLCBwcmVkX3Rlc3QsIGRmX3RyYWluJHRhcmdldCwgZGZfdGVzdCR0YXJnZXQpCnJlc3VsdHNbImNhdGJvb3N0IixdCmBgYAoKV2Ugb2J0YWluIGEgdHJhaW4gYWNjdXJhY3kgb2YgJDAuODgyJCBhbmQgYSB0cmFpbiBBVUMgb2YgJDAuOTIxJCwgdmVyc3VzIGEgdGVzdCBhY2N1cmFjeSBvZiAkMC44MzYkIGFuZCB0ZXN0IEFVQyAkMC44NTgkLiBUaGlzIHN1Z2dlc3RzIHRoYXQgdGhlIG1vZGVsIG1heSBiZSBvdmVyZml0dGluZyBmb3Igb3VyIHBhcnRpY3VsYXIgY2hvaWNlIG9mIHBhcmFtdGVycyAoYW5kIGFsc28gZm9yIG91ciBzdWJzYW1wbGVkIGRhdGFzZXQgc2l6ZSkuCgpGb3IgaW50ZXJwcmV0YWJpbGl0eSwgd2UgY2FuIHVzZSBDYXRCb29zdCdzICpmZWF0dXJlIGltcG9ydGFuY2UqLiBGcm9tIHRoZSBbQ2F0Qm9vc3QgZG9jdW1lbnRhdGlvbl0oI2h0dHBzOi8vY2F0Ym9vc3QuYWkvZG9jcy9jb25jZXB0cy9mc3RyLmh0bWwpLCBDYXRCb29zdCdzIGluLWJ1aWx0IGFuZCBkZWZhdWx0IGZlYXR1cmUgaW1wb3J0YW5jZSBkb2VzIHRoZSBmb2xsb3dpbmc6Cgo+IEZvciBlYWNoIGZlYXR1cmUsIFByZWRpY3Rpb25WYWx1ZXNDaGFuZ2Ugc2hvd3MgaG93IG11Y2ggb24gYXZlcmFnZSB0aGUgcHJlZGljdGlvbiBjaGFuZ2VzIGlmIHRoZSBmZWF0dXJlIHZhbHVlIGNoYW5nZXMuIFRoZSBiaWdnZXIgdGhlIHZhbHVlIG9mIHRoZSBpbXBvcnRhbmNlIHRoZSBiaWdnZXIgb24gYXZlcmFnZSBpcyB0aGUgY2hhbmdlIHRvIHRoZSBwcmVkaWN0aW9uIHZhbHVlLCBpZiB0aGlzIGZlYXR1cmUgaXMgY2hhbmdlZC4KCldlIGNvdWxkIGFsc28gc2V0IHRoZSB0eXBlIG9mIGZlYXR1cmUgaW1wb3J0YW5jZSB0byBTaGFwbGV5IFZhbHVlcywgYWx0aG91Z2ggd2Ugb21pdCB0aGUgcGxvdCBpbiB0aGlzIGNhc2UgZHVlIHRvIHRoZSBsYWNrIG9mIGluLWJ1aWx0IHN1cHBvcnQgZm9yIHBsb3R0aW5nLgoKIyMgUmVzdWx0cyBhbmQgRXZhbHVhdGlvbgoKYGBge3IgcmVzdWx0cywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KcmVzdWx0c1tvcmRlcihyZXN1bHRzJHRlc3RfYXVjLCByZXN1bHRzJHRlc3RfYWNjKSxdCmBgYAoKQWx0aG91Z2ggdGhlIHRyZWUtYmFzZWQgbWV0aG9kcyBhcmUgbGVzcyBpbnRlcnByZXRhYmxlIGFuZCBtb3JlICJibGFjayBib3giIHRvIHNvbWUgZXh0ZW50LCB3ZSBjYW4gbWFrZSB1c2Ugb2YgbW9kZWwgaW50ZXJwcmV0YWJpbGl0eSB0ZWNobmlxdWVzLgoKSXQgY291bGQgYmUgdGhhdCBvdXIgcmVzdXRscyBhcmUgbm90IGZ1bGx5IHJvYnVzdCwgZ2l2ZW4gd2UgaGF2ZSBzdWJzYW1wbGVkIHRoZSBvcmlnaW5hbCBkYXRhLgoKIyMgQ29uY2x1c2lvbgoKSW4gdGhpcyBwcm9qZWN0LCB3ZSBkZW1vbnN0cmF0ZWQgdGhlIHVzZSBvZiBsb2dpc3RpYyByZWdyZXNzaW9uLCBncmFkaWVudCBkZXNjZW50LCByZWd1bGFyaXNhdGlvbiwgcmFuZG9tIGZvcmVzdCwgZ3JhZGllbnQgYm9vc3RpbmcgYW5kIGh5cGVycGFyYW1ldGVyIHR1bmluZyB0ZWNobmlxdWVzIHRvIGFjaGlldmUgaW50ZXJwcmV0YWJpbGl0eSBhbmQgbW9kZWwgYWNjdXJhY3kgKGluIHNlcGFyYXRlIGNhc2VzKSBpbiBwcmVkaWN0aW5nIHRoZSBhbW91bnQgb2YgYW4gaW5zdXJhbmNlIGNsYWltLiBNb2RlbCBfX18gd2FzIHRoZSBtb2RlbCB3aXRoIGhpZ2hlc3QgQVVDLCBhbHRob3VnaCBpbnRlcnByZXRhYmlsaXR5IHdhcyBzYWNyaWZpY2VkLiBPbiB0aGUgb3RoZXIgaGFuZCwgb3VyIGJhc2VsaW5lIG1vZGVsIHdpdGggb25seSBhIGZldyBwcmVkaWN0b3JzIGlzIGVhc3kgdG8gaW50ZXJwcmV0LCBob3dldmVyIGl0IGhhcyBhIHJlbGF0aXZlbHkgbG93IEFVQyBvZiAwLjcwMy4gSW4gcHJhY3RpY2UsIGRlcGVuZGluZyBvbiB3aGV0aGVyIHRoZSBnb2FscyBvZiB0aGUgY29tcGFueSBsZWFuIHRvd2FyZHMgcHJlZGljdGlvbiBvciBleHBsYW5hdGlvbiwgYW4gYXBwcm9wcmlhdGUgdHJhZGUtb2ZmIGJldHdlZW4gbW9kZWwgY29tcGxleGl0eSBhbmQgaW50ZXJwcmV0YWJpbGl0eSBoYXMgdG8gYmUgY2hvc2VuLiAKCkZyb20gdGhlIGluc3VyZXLigJlzIHByb2ZpdCBwZXJzcGVjdGl2ZSwgdW5kZXJzdGFuZGluZyB3aGljaCBjaGFyYWN0ZXJpc3RpY3MgYXJlIGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggbGFyZ2UgYW1vdW50cyBvZiBjbGFpbXMgY291bGQgYWxzbyBiZSB1c2VkIHRvIGluZm9ybSB3aGV0aGVyIG9yIG5vdCBhIHBhcnRpY3VsYXIgaW5zdXJhbmNlIGFwcGxpY2F0aW9uIGlzIGFjY2VwdGVkLCBldmVuIGlmIGNhdXNhbGl0eSBjYW5ub3QgYmUgaW5mZXJyZWQuIEhvd2V2ZXIsIHRoaXMgbWlnaHQgbGVhZCB0byBzb21lIGxlZ2FsIGFuZC9vciBldGhpY2FsIGlzc3Vlcy4gRm9yIGV4YW1wbGUsIHRoZSBVS+KAmXMgMjAxMCBFcXVhbGl0eSBBY3QgbWFrZXMgZGlzY3JpbWluYXRpbmcgb24gbmluZSBwcm90ZWN0ZWQgY2hhcmFjdGVyaXN0aWNzLCBpbmNsdWRpbmcgYWdlLCBnZW5kZXIsIHJhY2UgYW5kIHJlbGlnaW9uLCBpbGxlZ2FsIChQYXR0bmksIDIwMjApLiBUaGVyZWZvcmUsIHByb3BlciBkYXRhIGdvdmVybmFuY2UgbmVlZHMgdG8gYmUgbWFpbnRhaW5lZCBhbmQgcHJpdmFjeSBpbXBhY3QgYXNzZXNzbWVudHMgbmVlZCB0byBiZSBjb25kdWN0ZWQgYmVmb3JlIGFueSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyBvZiB0aGlzIGtpbmQgYXJlIGRlcGxveWVkIGluIGluZHVzdHJ5IChHRFBSLCAyMDE4KS4KCgoKIyMgQmlibGlvZ3JhcGh5Cgo6OjogeyNyZWZzfQo6OjoKCiMjIEFwcGVuZGl4CgojIyMgQ29tcGFyaXNvbiBvZiBDb2VmZmljaWVudHMgd2l0aCBPdXRsaWVycyBSZW1vdmVkCgpgYGB7ciByZW1vdmVkLW91dGxpZXJzfQppbmZsdWVuY2VycyA8LSBhcy5udW1lcmljKHJvd25hbWVzKGluZmx1ZW5jZVBsb3QoZ2xtX2Z1bGwpKSkKZ2xtX2Z1bGxfaW5mbHVlbmNlcnMgPC0gdXBkYXRlKGdsbV9mdWxsLCBzdWJzZXQgPSBjKC1pbmZsdWVuY2VycykpCmdsbV9mdWxsX291dGxpZXJzIDwtIHVwZGF0ZShnbG1fZnVsbCwgc3Vic2V0ID0gYygtb3V0bGllcnMpKQpyZW1vdmFsX2xpc3QgPC0gdW5pb24ob3V0bGllcnMsIGluZmx1ZW5jZXJzKQpnbG1fZnVsbF9yZW1vdmVkIDwtIHVwZGF0ZShnbG1fZnVsbCwgc3Vic2V0ID0gYygtcmVtb3ZhbF9saXN0KSkKY29tcGFyZUNvZWZzKGdsbV9mdWxsLCBnbG1fZnVsbF9pbmZsdWVuY2VycywgZ2xtX2Z1bGxfb3V0bGllcnMsIGdsbV9mdWxsX3JlbW92ZWQpCmBgYAoKYGBge3J9CnNldC5zZWVkKDIwMjEpCmN2X3NwbGl0cyA8LSByc2FtcGxlOjp2Zm9sZF9jdihkZl90cmFpbiwgc3RyYXRhID0gdGFyZ2V0LCB2PSAzKQptb2QgPC0gbG9naXN0aWNfcmVnKHBlbmFsdHkgPSB0dW5lKCksCiAgICAgICAgICAgICAgICAgICAgbWl4dHVyZSA9IHR1bmUoKSkgJT4lCiAgc2V0X2VuZ2luZSgiZ2xtbmV0IikKCmdsbW5ldF9yZWNpcGUgPC0gcmVjaXBlKHRhcmdldH4uLGRhdGEgPSBkZl90cmFpbikgJT4lIAogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgLWFsbF9vdXRjb21lcygpKQoKZ2xtbmV0X3dvcmtmbG93PC0gd29ya2Zsb3coKSAlPiUKICBhZGRfcmVjaXBlKGdsbW5ldF9yZWNpcGUpICU+JQogIGFkZF9tb2RlbChtb2QpCgpnbG1uX3NldCA8LSBwYXJhbWV0ZXJzKHBlbmFsdHkocmFuZ2UgPSBjKC01LDEpLCB0cmFucyA9IGxvZzEwX3RyYW5zKCkpLAogICAgICAgICAgICAgICAgICAgICAgIG1peHR1cmUoKSkKCmdsbW5fZ3JpZCA8LSAKICBncmlkX3JlZ3VsYXIoZ2xtbl9zZXQsIGxldmVscyA9IGMoNywgNSkpCmN0cmwgPC0gY29udHJvbF9ncmlkKHNhdmVfcHJlZCA9IFRSVUUsIHZlcmJvc2UgPSBUUlVFKQoKZ2xtbl90dW5lIDwtIAogIHR1bmVfZ3JpZChnbG1uZXRfd29ya2Zsb3csCiAgICAgICAgICAgIHJlc2FtcGxlcyA9IGN2X3NwbGl0cywKICAgICAgICAgICAgZ3JpZCA9IGdsbW5fZ3JpZCwKICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpY19zZXQocm9jX2F1YyksCiAgICAgICAgICAgIGNvbnRyb2wgPSBjdHJsKQoKCmJlc3RfZ2xtbiA8LSBzZWxlY3RfYmVzdChnbG1uX3R1bmUsIG1ldHJpYyA9ICJyb2NfYXVjIikKCmdsbW5ldF9tb2RlbCA8LSAKICBsb2dpc3RpY19yZWcoKSAlPiUKICBzZXRfZW5naW5lKCJnbG1uZXQiLCBzZWVkPTIwMjEpICU+JQogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCgoKCmdsbV9yZWcgPC0gZml0KGdsbW5ldF93b3JrZmxvdywgZGF0YSA9IGRmX3RyYWluKQpgYGAKCgpgYGB7cn0KeGdiX3NwZWMgPC0gYm9vc3RfdHJlZSgKICB0cmVlcyA9IDEwMCwgCiAgdHJlZV9kZXB0aCA9IHR1bmUoKSwKICBsZWFybl9yYXRlID0gdHVuZSgpICAgICAgICAgICAgICAgICAgICAgICAgICMjIHN0ZXAgc2l6ZQopICU+JSAKICBzZXRfZW5naW5lKCJ4Z2Jvb3N0IikgJT4lIAogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCgp4Z2JfZ3JpZCA8LSBncmlkX2xhdGluX2h5cGVyY3ViZSgKICB0cmVlX2RlcHRoKCksCiAgbGVhcm5fcmF0ZSgpLAogIHNpemUgPSA1CikKCnhnYl93ZiA8LSB3b3JrZmxvdygpICU+JQogIGFkZF9mb3JtdWxhKHRhcmdldCB+IC4pICU+JQogIGFkZF9tb2RlbCh4Z2Jfc3BlYykKCnNldC5zZWVkKDEyMykKdmJfZm9sZHMgPC0gdmZvbGRfY3YoZGZfdHJhaW4sIHN0cmF0YSA9IHRhcmdldCkKCmRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbCgpCgpzZXQuc2VlZCgyMzQpCnhnYl9yZXMgPC0gdHVuZV9ncmlkKAogIHhnYl93ZiwKICByZXNhbXBsZXMgPSB2Yl9mb2xkcywKICBncmlkID0geGdiX2dyaWQsCiAgY29udHJvbCA9IGNvbnRyb2xfZ3JpZChzYXZlX3ByZWQgPSBUUlVFKQopCmJlc3RfYXVjIDwtIHNlbGVjdF9iZXN0KHhnYl9yZXMsICJyb2NfYXVjIikKZmluYWxfeGdiIDwtIGZpbmFsaXplX3dvcmtmbG93KAogIHhnYl93ZiwKICBiZXN0X2F1YwopCmxpYnJhcnkodmlwKQoKZmluYWxfeGdiICU+JQogIGZpdChkYXRhID0gdmJfdHJhaW4pICU+JQogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lCiAgdmlwKGdlb20gPSAicG9pbnQiKQoKZmluYWxfcmVzIDwtIGxhc3RfZml0KGZpbmFsX3hnYiwgZGZfc3BsaXQpCgpjb2xsZWN0X21ldHJpY3MoZmluYWxfcmVzKQoKeGdiX3JlcyAlPiUKICBjb2xsZWN0X21ldHJpY3MoKSAlPiUKICBmaWx0ZXIoLm1ldHJpYyA9PSAicm9jX2F1YyIpICU+JQogIHNlbGVjdChtZWFuLCBtdHJ5OnNhbXBsZV9zaXplKSAlPiUKICBwaXZvdF9sb25nZXIobXRyeTpzYW1wbGVfc2l6ZSwKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIiwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAicGFyYW1ldGVyIgogICkgJT4lCiAgZ2dwbG90KGFlcyh2YWx1ZSwgbWVhbiwgY29sb3IgPSBwYXJhbWV0ZXIpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuOCwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofnBhcmFtZXRlciwgc2NhbGVzID0gImZyZWVfeCIpICsKICBsYWJzKHggPSBOVUxMLCB5ID0gIkFVQyIpCmBgYAoKYGBge3J9CiJodHRwczovL2N1cnNvLXIuZ2l0aHViLmlvL3RyZWVzbmlwL2luZGV4Lmh0bWwiCmxpYnJhcnkodHJlZXNuaXApCmBgYAo=